Мы надеемся, что вам понравилась предыдущая заполненная мемами часть первая этой серии сообщений в блоге, в которой мы объяснили историю нашего веб-приложения и почему AngularJS пришлось полностью переработать в Angular, чтобы оставаться конкурентоспособным.

Из-за значительных изменений в упомянутом переписывании мы знали, что наша работа была вырезана для нас, поэтому в течение нескольких месяцев мы планировали и медленно преобразовывали код нашего приложения в соответствии с новыми шаблонами, которые применялись в Angular. Это помогло снизить риск во время обновления и, как положительный побочный эффект, также повысило производительность и тестируемость. Эта статья, вторая часть серии, будет посвящена высокоуровневым изменениям, которые мы внесли перед изменением 500 тыс. Строк кода. Это включало обновление с ES5 до TypeScript, разбиение нашего приложения на компоненты, обработку динамических шаблонов, обход событий и подготовку Protractor для обеспечения надежности обновления.

JavaScript (ES5) в TypeScript

Исходный код Angular не просто построен с использованием TypeScript - они также настоятельно рекомендуют разработчикам, использующим фреймворк Angular, создавать свои приложения с его помощью. Как вы, вероятно, можете понять по названию, он дает набор текста для обычного JavaScript, чтобы помочь разработчикам фиксировать ошибки на более раннем этапе в процессе транспилирования. В приведенном ниже примере показано, как это будет работать.

Не убежден? Рассмотрим эти другие веские причины для перехода:

  • Любой тип переключения языка программирования потребует огромных усилий, но, к счастью, в отличие от других препроцессоров JS, таких как CoffeeScript или Dart, TypeScript является полностью необязательным, что означает, что вам не нужно использовать преимущества набора текста, если вы этого не хотите. Это означает, что вам не нужно преобразовывать всю базу кода в TypeScript за один раз, потому что вы можете технически преобразовать все свои файлы .js и дать им расширение .ts, не меняя ничего другого, и он все равно сможет компилироваться (при условии, что у вас есть подходящие инструменты для транспиляции). Вы можете изменять каждый файл .js один за другим - это гораздо более управляемая задача.
  • Почти вся документация и примеры кода Angular написаны на TypeScript. Если бы мы решили продолжить работу с ES5, это значительно снизило бы нашу производительность. Нашим разработчикам придется не только изучить новый фреймворк, но и придумать, как преобразовать те же примеры кода в ES5.
  • Инструменты для TypeScript значительно улучшились за последние несколько лет:
  • … .. Angular языковые службы обнаруживают множество ошибок шаблона еще до того, как вы попытаетесь выполнить предварительную компиляцию (AOT).
  • … .. TSLint - это мощный инструмент линтинга для TypeScript, который обеспечивает определенные стандарты кодирования (невозможно с JSHint). TSLint также принимает настраиваемые плагины, поэтому, если вам нужно обеспечить соблюдение нового правила, например, отсутствие изолированных модульных тестов с «fdescribe» или «fit», это легко достижимо.
  • … .. TypeScript import r избавляет нас от утомительных задач, таких как написание операторов импорта в верхней части файла.
  • … .. Visual Studio Code и TypeScript созданы и поддерживаются Microsoft, поэтому, как пара IDE и языка программирования, они созданы на небесах. Он также может отслеживать определения на несколько уровней, поэтому навигация по исходному коду приложения становится более интуитивной.

Если вы решите перейти на TypeScript, помните об этих распространенных ошибках:

  • Ограничьте использование типа любой. Это простой костыль, на который можно положиться, когда вы исправляете ошибки компилятора TypeScript. Однако в конечном итоге это навредит вам, потому что не удастся выявить опечатки на ранней стадии. С таким же успехом вы можете писать ES5.
  • Не пытайтесь преобразовать все сразу. Лучше начать с самого низкого уровня и продвигаться вверх. В структуре нашего приложения AngularJS есть модели, которые импортируются в сервисы, а затем импортируются в компоненты (см. Диаграмму ниже).

Если вы начнете с моделей (которые можно считать основой приложения), вы воспользуетесь преимуществами набора текста по мере продолжения преобразования.

Компоненты и односторонняя привязка

До выпуска React AngularJS следовал шаблону модель-представление-контроллер. Вы можете напрямую привязаться к переменным в директиве, а также передавать и обновлять ее с помощью двусторонней привязки. Это было похоже на волшебство в том смысле, что оно было почти мгновенным и сделало управление состоянием приложения очень простым - но также и непредсказуемым (подробнее об этом позже).

Директивы AngularJS и двусторонняя привязка данных http://plnkr.co/edit/kADe706q2aW7xwqOnYZB?p=preview

Если вы нажмете на демо-ссылку выше, вы увидите небольшое веб-приложение, которое отображает «Эрика» в поле ввода и в div. Если затем ввести в поле ввода, вы увидите, что переменная name обновляется почти мгновенно. Что еще более важно, это изменение инициируется дочерней директивой ‹name-input› - и из-за двусторонней привязки это изменение распространяется по всему приложению.

Если вы откроете инструменты разработчика и внимательно посмотрите на исходный код в script.js, вы увидите, что мы привязываемся непосредственно к двусторонней привязке name в ‹name -вход ›. Все компоненты также $ следят за своим собственным экземпляром имени и записывают его в журнал, как только видят изменение. Вы увидите, что любое изменение, внесенное в поле ввода, означает, что первым обновляется ‹name-input›, но на самом деле его брат, ‹name-display› обновляется раньше, чем его родительский элемент ‹name-container›, что может привести к возможным условиям гонки.

Теперь Angular пошел по стопам React, поддерживая идею о том, что все является компонентом. Страницу можно рассматривать как родительский компонент, который содержит состояние приложения и передает это состояние дочерним компонентам через односторонние привязки данных. Чтобы обновить это состояние, родительский компонент будет передавать входную привязку обратного вызова дочерним элементам, чтобы состоянием можно было управлять только одним способом. См. Плункер ниже для примера того же имени, но с односторонней привязкой данных.

См. Ссылку на плункер, предоставленную здесь. Http://plnkr.co/edit/PMMCWi89Gv7rqaHYMQSt?p=preview

Если вы снова откроете инструменты разработчика и посмотрите исходный код в script.js, вы увидите, что мы изменили несколько вещей:

  • Мы преобразовали предыдущие директивы в компоненты, которые имеют другой, но более простой синтаксис.
  • Из родительского ‹name-container›, мы передаем name его дочерним компонентам через привязки ввода. Мы также передаем обратный вызов onNameChange в ‹name-input›, чтобы он мог обновлять состояние всякий раз, когда кто-то манипулирует полем ввода.
  • Если вы сейчас посмотрите журналы консоли, мы фактически используем жизненный цикл $ onChanges, который идет с каждым компонентом. Как видите, мы сначала манипулируем родителем, а затем передаем его детям. Это гораздо более предсказуемо, потому что сначала родитель видит изменение, а потом его видят потомки.

До: Директива ES5 с двусторонней привязкой данных

После: компонент TypeScript с односторонней привязкой данных

Динамические шаблоны

Одной из функций, которые мы широко использовали в AngularJS, была функция шаблона, которая позволяла нам динамически загружать различный HTML-контент на основе атрибутов. Это было особенно полезно для нашего веб-приложения, потому что мы постоянно проводим различные A / B-эксперименты. Отдельный однофункциональный HTML-файл легче поддерживать, чем один HTML-файл, заваленный операторами ng-if.

Динамические шаблонные функции в AngularJS

Увы, в Angular этой функции больше нет. Но есть два обходных пути для достижения того же результата:

  • Используйте ng-switch для переключения шаблона для загрузки. В ES6 мы легко достигли этого, импортировав их как переменные и отрисовав их как часть строки шаблона (фрагмент кода см. Ниже). Имейте в виду, что для этого требуется родительский контейнер для включения ng.
  • Создайте отдельный компонент для каждого уникального файла шаблона, но используйте один и тот же базовый контроллер, чтобы уменьшить повторение кода.

Использование ng-switch для шаблонов

Отдельные компоненты с одним и тем же базовым контроллером

Больше никаких мероприятий

AngularJS дал разработчикам возможность публиковать пользовательские события и подписываться на них с помощью $ emit, $ on и $ broadcast. Это позволяло им обмениваться данными в приложении или отправлять данные назад и вперед от родительского и дочернего компонентов. Поскольку Angular поощряет архитектуру одностороннего потока данных (см. Раздел «Компоненты и односторонняя привязка» выше), наличие этой функции было бы анти-шаблоном, поскольку родители могут передавать данные своим детям через привязки ввода, а дети могут передавать данные обратно родителю через обратные вызовы.

Однако мы живем в реальном мире, и у вашей команды может просто не хватить времени на то, чтобы заново спроектировать все, чтобы следовать этому шаблону. Хорошей новостью является то, что вы можете создать свою собственную реализацию менеджера событий, и она будет абсолютно нормально работать в Angular. Плохая новость в том, что он не поддерживает обход дерева компонентов.

  • $ broadcast отправляет сигнал события дочерним компонентам вниз.
  • $ emit отправляет сигнал события вверх родительским компонентам.
  • $ on - это подписка на сигнал события.

Если ваше приложение имеет даже среднюю сложность, то наличие этих сигналов вверх и вниз по вашему приложению может снизить вашу производительность, поскольку оно должно проходить по дереву компонентов и по существу повторять сигнал на каждом уровне. Более эффективный способ сделать это - подписаться ($ on) или опубликовать ($ emit) на $ rootScope, что означает, что он должен активироваться только один раз. Если вы уже внесли это изменение, наша реализация EventsManager подойдет вам. Подробности см. Ниже.

Обновления транспортира

Модульные тесты - отличный способ протестировать отдельные компоненты или сервисы, но для проверки пользовательских потоков мы в значительной степени полагаемся на сквозные (E2E) тесты с использованием Транспортира. E2E-тесты великолепны, потому что они фактически взаимодействуют с веб-приложением так же, как и обычный пользователь, но они могут быть нестабильными. Существует несколько причин, по которым тест может завершиться неудачно, включая, помимо прочего, проблемы с данными среды, проблемы с синхронизацией, невосприимчивость к селену, различия браузеров, непреднамеренное прерывание вручную и многое другое. Мы создали специальные инструменты и принудительную структуру тестового кода, чтобы обойти некоторые из этих проблем, но это тема для другого дня, если будет достаточно интереса. (Оставьте нам комментарий, если хотите услышать об этом больше.) В конечном итоге именно наши E2E-пакеты придали нам уверенности в том, что мы можем продвигаться вперед с выпуском в производство.

Чтобы наши автоматические тесты запускались на Angular, нам пришлось обновить наши привязки, чтобы они не полагались на привязки, специфичные для AngularJS.

  • By.model - ищет ng-модель в DOM.
  • By.binding - ищет ng-bind в DOM.
  • By.repeater - ищет ng-repeat в DOM.

Мы обошли это, создав специальный атрибут с уникальным префиксом «at-» для тех же элементов и соответствующим образом обновив наши селекторы. Мы решили сопоставить атрибут с классом CSS, потому что обнаружили, что слишком легко забыть обновить объекты страницы транспортира. Это особенно верно, если у вас есть команда, специализирующаяся на дизайне и CSS, где их основное внимание уделяется рефакторингу, чтобы уменьшить влияние CSS на приложение.

В том, что все? (Сарказм)

Это самые большие изменения, с которыми нам пришлось столкнуться перед попыткой обновления Angular. У каждой компании будет свой набор проблем, но, надеюсь, этот пост поможет вам сделать огромный шаг к новой структуре. Следующий пост из нашей серии из четырех частей будет посвящен нашему сценарию преобразования и тому, как он автоматически преобразовал 90% нашего исходного кода.

Если вы хотите узнать больше о нашем обновлении, обязательно ознакомьтесь с публикацией Сунг Чой: Как я научился останавливать AngularJS и полюбить Angular и посмотрите видео выступления Эрика из Встреча AngularNYC: От AngularJS к Angular:« цельный подход».

Хотите узнать больше о возможностях нашей команды? Посетите страницу вакансий в Grubhub.