Ранее: Часть вторая: технические изменения

Обучение

Даже в течение первого месяца конверсии сразу стало очевидным несколько лучших стратегий. Взгляд в прошлое 20/20 неизбежен. Мы учились на своих ошибках и работали над улучшением нашего поверхностного поведения и основополагающих рассуждений.

Не меняйте JavaScript

Каждая ошибка, которую мы вводили как часть преобразования TypeScript, была связана с изменением JavaScript, которое мы считали безопасным. Каждый из! Мы попали в то, что Дэн Вандеркам называет Жуткой Долиной Типов.

При изменении файла, например при преобразовании его с одного языка на другой, вы можете:

  • Проверяйте каждую измененную строку вручную
  • Полагайтесь на свои шаги сборки, чтобы отловить ошибки

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

Мы на собственном горьком опыте убедились, что во многих местах охват тестированием не был особенно высоким, и внесение произвольных изменений JavaScript в PR, затрагивающих десятки файлов, было небезопасным. Если вы были пользователем Codecademy в 2019 году, и у вас произошел сбой сайта, возможно, это было из-за преобразования TypeScript? 😉

Кто-то, вероятно, мог бы написать инструмент для отметки изменений в выходном JavaScript при преобразовании TypeScript. Это было бы полезно. Ах хорошо.

Единственный верный путь

Выберите один способ сделать что-нибудь и придерживайтесь его. Даже для таких вариантов, как interface или type, которые практически не имеют значения, хорошо иметь единый рекомендуемый подход. Различия сбивают с толку новичков и вводят новые ненужные темы для изучения.

Объявление компонентов React

Например, есть много разных способов объявить компонент React в TypeScript. Вы используете interface и function? Как насчет type и () =>?

В конце концов мы остановились на React.FC для компонентов React. React.FC добавляет поле children?: ReactNode к любому типу свойств, который вы ему даете, что полезно для того, чтобы вам не приходилось вручную повторно объявлять ваш общий тип children.

Interface or Type?

Как следует из приведенных выше фрагментов, мы решили type вместо interface. Мы уже склонялись к types для продвинутых типов, поэтому имело смысл консолидироваться на использовании только одной из этих двух почти идентичных конструкций.

Единственные места, где мы по-прежнему намеренно используем interface, - это изменение глобальных объявлений, таких как Window.

Минимальные объявления типов

Давным-давно многие программисты TypeScript решили предпочесть правило TSLint [typedef](<https://palantir.github.io/tslint/rules/typedef>), которое предписывало добавлять объявления типов TypeScript : в места, независимо от того, будет ли TypeScript выводить тип в любом случае. Вот несколько распространенных причин:

  • Многие ранние последователи TypeScript пришли из типизированных языков, таких как C #, которые поощряли или требовали объявления типов вручную.
  • typdef документация TSLint не рекомендует этого делать, что может быть истолковано как рекомендация по его включению.
  • Возможности IDE проявляются ближе к тому месту, где функции и переменные объявляются с типами, а не тогда, когда их предполагаемые типы используются неправильно в других местах.

Мы очень не одобряем typedef и в целом практику аннотаций избыточного типа. Мы бы предпочли использовать правило против ненужных аннотаций типов.

  • Любой лишний набор текста, чтобы заставить вещи работать, создает дополнительные трудности для написания кода. Мы не хотим тратить дополнительное время на чтение или написание нашего веб-кода.
  • TypeScript очень хорош в выводе типов и за последние несколько лет дошел до того, что его выводы часто лучше, чем то, что вы обычно набираете.
  • Сложные объектные литералы особенно раздражают при написании деклараций типов, когда несколько строк создаются встроенными в функцию.

Команда TypeScript теперь не рекомендует typdef:

Рискуя выпустить редкую декларацию стиля от команды TypeScript, мы настоятельно не рекомендуем применять правило typedef. Во многих случаях это активно вредно, и мы не собираемся добавлять языковые функции для обхода правила lint.

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

Определенно сбивает с толку

Мне еще предстоит найти хороший способ представить объединенные концепции .d.ts файлов и @types новичку в TypeScript. Они одновременно необходимы для любого проекта TypeScript с множеством зависимостей npm и требуют некоторого глубокого понимания того, как работает система типов. Что такое алгоритм поиска модулей в системе типов? Как работают модули с подстановочными знаками? Что значит неправильное объявление типа?

Одна неверная строка в .d.ts файле может иметь катастрофические последствия для энтузиазма TypeScript, если она мешает кому-либо выполнять свою работу. Мы обнаружили, что необходимо громко заявить о том, что команда предпочитает удалить любые неприятные @types/ определения, когда они вызывают проблемы - таким образом, any-приведение объявления модуля.

Чтобы было ясно: проблема не в том, что сегодняшние системы объявления типов TypeScript плохие или недокументированные; Дело в том, что их крайние случаи принципиально трудно понять, если вы не получили полного представления о системах постепенного ввода.

Кстати, мы обнаружили, что наш .d.ts формат для импорта ресурсов в стиле Webpack помогает объяснить, как они работают:

@types/react Дублирование

С вами такое случалось?

node_modules/@types/react-dom/node_modules/@types/react/index.d.ts:2777:14 - error TS2300: Duplicate identifier 'LibraryManagedAttributes'.
2777         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                  ~~~~~~~~~~~~~~~~~~~~~~~~
  node_modules/@types/react/index.d.ts:2776:14
    2776         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                      ~~~~~~~~~~~~~~~~~~~~~~~~
    'LibraryManagedAttributes' was also declared here.

Казалось, что каждый раз, когда мы обновляли пакет, связанный с React, в конечном итоге появлялись две версии глобальных типов React.

Каждый. Черт возьми. Время.

Эта проблема вызвана наличием нескольких несовместимых версий semver из @types/react, которые затем были подхвачены компилятором TypeScript. Вы можете проверить, что происходит не так, проверив свой _35 _ / _ 36_ на наличие нескольких @types/react разрешений версий.

Решение обычно состоит из одного или обоих из следующих вариантов:

  • Одновременное обновление пакетов, связанных с React, до последних версий
  • Ручное редактирование этого файла блокировки (тьфу), чтобы указать только одну версию

Быстрый поиск в Google по этой проблеме показывает, что мы определенно не единственные.

Экосистема Redux, UGH

Состояние Redux само по себе - замечательная система для моделирования на типизированном языке. Состояние на самом деле представляет собой серию объявлений типа; действия - фантастический пример использования дискриминируемых союзов; Использование редукторов ... полностью поддерживается TypeScript.

Но вау разваливается ли ваша типизация, когда вы добавляете redux-actions, redux-saga, redux-saga-test-plan и другие, казалось бы, бесконечные помощники Redux, которые мы использовали. Каждая библиотека сама по себе, как правило, хороша, но их объединение может быть проблематичным. Iterable из одной библиотеки работает как IterableIterator в другой? Как undefined стихи void возвращаемые значения взаимодействуют? Как насчет трех-четырех слегка разных Action объявлений типов? 😖.

Мы обнаружили, что any кастинг повсюду в наших тестовых файлах саги. Некоторые битвы лучше вести после того, как вы накопите гораздо больше месяцев коллективного опыта TypeScript, чем мы сейчас.

Следующие шаги

Очень немногие технологические сдвиги действительно завершены после первой волны внедрения. Мы отправили MVP и остались довольны результатами. Если 2019 год был годом принятия TypeScript, то 2020 год - годом совершенствования его использования. Время повторить!

Ссылки на проекты

TypeScript добавил возможность разбивать один проект TypeScript на отдельные tsconfig.json, которые ссылаются друг на друга. Это значительно сокращает время инкрементной проверки типов, улучшает принудительное выполнение внутренних графиков зависимостей проекта и дает возможность использовать разные настройки компилятора для разных областей кодовой базы.

Наша команда разработчиков веб-платформы постепенно отказывается от усилий, чтобы лучше отобразить зависимости проектов между нашими системами. Например, служебные программы, которые существуют в каталогах для нашей сложной среды обучения, не должны импортироваться кодом пользовательской панели мониторинга; У коммунальных сетей есть свое место. На данный момент мы используем no-restricted-syntax правило ESLint в нескольких .eslintrc.json проектах:

Ограничения на импорт будут намного чище и станут менее заметными, если они будут введены компилятором TypeScript.

Мы также хотели бы использовать различные настройки компилятора для исходных файлов, модульных и сквозных тестов. Тестовые глобальные объекты, такие как describe() и it(), не должны быть доступны в коде, поставляемом в рабочую среду. Тестовые библиотеки Jest и TestCafe объявляют разные test глобальные переменные, поэтому их нельзя включать одновременно.

Any Отливки

По состоянию на неделю после завершения первоначального преобразования в нашем коде было чуть более 600 типов as any и : any. Дюжина находится в .d.ts объявлениях, ~ 250 - в тестах, а оставшиеся ~ 350 - в исходных файлах.

Кроме того, запуск средства проверки типов TypeScript с включенным --noImplicitAny сообщает примерно 2500 неявных any с. ~ 400 из них находятся в тестовых файлах; оставшиеся ~ 2100 находятся в исходных файлах.

У нас явно есть над чем поработать, чтобы завершить охват типов. Наш план по очистке наших типов:

  1. Включите флаг компилятора --noImplicitAny - вероятно, для каждого проекта после добавления вышеупомянутых ссылок на проекты tsconfig.json
  2. Включите правило typescript-eslint no-explicit-any с отключением трудно исправляемых исключений вручную с помощью комментариев // eslint-disable
  3. Уберите все эти // eslint-disable комментарии

Автоматические --noImplicitAny исправления

Служба языка TypeScript может предоставить предлагаемое исправление для добавления недостающего типа к параметру или переменной, которая неявно имеет тип any. Это очень полезно для быстрых исправлений во время разработки. TypeStat может программно извлекать все эти исправления и многократно применять их.

К сожалению, все еще есть много случаев, когда TypeScript дает бесполезные, даже причудливые предложения типа: # 28991, # 29321, # 29322, # 29324. Мы хотели бы найти время, чтобы внести исправления для этих проблем в TypeScript. У нас почти наверняка не хватит времени на всех, поэтому, пожалуйста: если вы читаете это, мы были бы рады, если бы вы помогли! 🙌

Больше линтинга

Наша конфигурация ESLint в основном состоит из правил, оставшихся до преобразования TypeScript. Мы до сих пор не рассмотрели впечатляюще обширный список правил машинописного текста. У каждого из этих правил есть билет JIRA в нашем бэклоге с запросом на:

  1. Выясните, будет ли правило полезно для нас, и если да, то о скольких нарушениях оно сообщит, если будет включено.
  2. В зависимости от количества нарушений: если их всего несколько, быстро исправляйте; в противном случае подайте контрольные тикеты для каждой области кода, чтобы исправить несколько нарушений за раз.
  3. Как только все нарушения будут устранены, активируйте правило в целом.

Разделить все!

Разделение нашей работы по вовлечению многих членов команды в позднюю стадию преобразования TypeScript сослужило нам хорошую службу. Мы уже планируем снова использовать его на всех трех этапах any приведения и включения правил lint.

В заключение

Нет двух одинаковых технологических сдвигов, но эти клиенты помогли нам успешно преобразовать TypeScript:

  • Глубоко подумайте об изменениях в технологиях - сначала о том, стоят ли они того, учитывая насущные потребности вашей команды, а затем о том, как лучше всего их достичь.
  • Не забывайте сосредотачиваться на внутреннем обмене знаниями и евангелизации, особенно при выполнении задач начального уровня.
  • Не бойтесь адаптировать свой план игры в режиме реального времени, чтобы минимизировать трение разработчиков по мере изучения технологии.
  • Выясните типичные вопросы и проблемы пользователей и разработайте простые шаблоны для их решения.
  • @ Babel / preset-typescript, typescript-eslint и TypeStat были ключом к нашему успешному преобразованию TypeScript.

Если ваша база кода JavaScript превышает несколько десятков файлов, мы настоятельно рекомендуем серьезно подумать о TypeScript. Это может показаться более пугающим, чем есть на самом деле. Дайте нам знать, если у вас есть вопросы или комментарии - мы будем рады помочь!

💖, команда Codecademy Frontend