Как мы на React 16 переезжали

https://blog.discordapp.com/lessons-from-migrating-a-large-codebase-to-react-16-e60e49102aa6
image

Facebook переписал большую часть React'а и выпустил 16 версию. React 16 был очень ожидаемым обновлением, особенно ввиду нового способа рендеринга Fiber, который сильно повышает производительность. Команда разработчиков React в последней версии усердно помечала методы и пакеты устаревшими (deprecated), и мы видели их предупреждения в консоли. В действительности же, миграция не так проста для большого проекта.

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

Зачем мы это сделали?


Архитектура Fiber выглядит так, что она сможет сильно повысить плавность и скорость работы как рендеринга DOM-дерева, так и Native-компонентов путем разбиения процесса рендеринга на составляющие, давая предпочтение более быстрым частям. Прямо сейчас не очень ясно, являются ли эти преимущества существенными (например, асинхронный рендеринг все еще не включен), но эти изменения прокладывают путь в светлое будущее.

В конечном счете, вам придется обновиться. React находится в движении, и 16+ сообщество обязательно оставит вас брошенным наедине с багами. А в качестве бонуса React 16 приносит новые фичи, например, ErrorBoundary. В нем множество изменений, к которым вам придется привыкнуть.

Геморрой начинается здесь


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

Мы писали качественный код, но этого оказалось недостаточно, чтобы легко перейти с миксинов и React.createClass. Мы также использовали конкретные версии пакетов, что давало нам стабильность и уверенность, но наши зависимости стали старыми и покрылись мхом.

Вот здесь мы и столкнулись с болью от миграции.

Запуск codemod


Прежде всего вы должны использовать codemod, чтобы переехать с React.PropTypes на prop-types, и мигрировать с React.createClass. Codemod поможет в правильном выборе: использовать class Component где это возможно (или PureComponent в случае простого миксина), а где использовать create-react-class.

Установите jscodeshift, а склонируйте react-codemod репозиторий. Затем запустите следующие команды для вашего проекта:

1. Переход на prop-types

jscodeshift -t ./react-codemod/transforms/React-PropTypes-to-prop-types.js /path/to/your/repo

2. Переход на Component, PureComponent или createReactClass:

jscodeshift -t ./react-codemod/transforms/class.js --mixin-module-name=react-addons-pure-render-mixin --flow=true --pure-component=true --remove-runtime-proptypes=false /path/to/your/repo

Последнее — это последовать хорошо описанным советам в репозитории react-codemod. Наиболее важные из них:

  • Переход на ES6-классы, если нет миксинов, и на PureComponent, в случае простых миксинов.
  • Убедиться, что если в созданных классах присутствуют все static-параметры.
  • Созданы аннотации из React.propTypes.
  • Перенесены привязывание контекста

    this.method.bind(this)

    методов из конструкторов в стрелочные функции (если, конечно, это вам по душе).
  • Пропущены все компоненты, которые используют устаревшее API, например, isMounted() метод, и так далее.

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

Если вам не понравится это автоизменение, вы можете поправить вручную что угодно на свой вкус. Codemod — это прекрасный инструмент, но не волшебная палочка. Он не мигрирует проект полностью за вас!

Поиск и замена


Мы обнаружили множество мест в коде, которые пропустил codemod. Судя по всему, там, где мы импортировали и вызывали PropTypes напрямую. Больше всего ручных исправлений пришлось на выискивание вызовов метода isMounted() и создание свойства _isMounted, которая равнялась true внутри componentDidMount и false внутри componentWillUnmount (но, возможно, вам так делать не стоит)

Отказ от использования приватного API


Конечно же, вы не используете внутренние API фреймворка React, верно? Те, которые лежат в react/lib/*. Если вы в своем проекте полагаетесь на них, то для вас плохие новости: вы за бортом.

Некоторые API были перемещены во внешние библиотеки (Семейство пакетов «react-addons-»), а некоторые, вовсе, были удалены.

В нашем проекте мы полагались на самописный TransitionGroup, которые зависел от flattenChildren-функции из недр react/lib. Но теперь этой функции нет, как и childMapping и mergeCildMapping. Разработчики фреймворка в таком случае рекомендуют просто взять и скопировать эти недостающие функции. Это мы в итоге и сделали. Однако, мы встроили новые версии тех функций из пакета react-transition.

Таким образом, мы столкнулись с первой серьезной проблемой. Но наше приложение по прежнему не работало.

Обновление зависимостей


Этот пункт оказался одним из самых утомительных. И если вы не лихорадите обновлением до 16 версии, разумно будет подождать, пока большинство библиотек обновятся. Если вы хотите заняться обновлением, то это поможет каждому.
image
Консоль по прежнему показывает предупреждения о пакетах, которые используют React.PropTypes или React.createClass. Вам нужно будет обновить эти пакеты. Либо придется их заменить, либо как-то обойти, написав свое решение. Или же, форкнуть и поправить их, если пакеты кажутся заброшенными.

Основные проблемы, которые нужно решить:

  1. React.PropTypes
  2. React.createClass
  3. Зависимости от внутренностей фреймворка версии меньше 16

К сожалению, не все ошибки легко найти, и здесь кроется настоящий геморрой.

Столкнувшись с ошибкой, целых два дня ушло на поиск искомой библиотеки. И у вас будет тоже самое. Консоль продолжала говорить нам, что reactRiberChild не может зарендерить элемент, потому что атрибут ref был undefined. Мы рвали волосы на голове, выискивая что могло пойти не так, и в конечном счете копнули внутрь кода React, где увидели причину ошибки. Оказалось, что 16-й версии React не нравится ref: undefined, а вот null вполне себе ок. После этого стало понятно, что создаваемые элементы с помощью одной внешней библиотеки имели атрибут ref: undefined. После того, как мы форкнули библиотеку, модифицировав ее код, все заработало прекрасно.

Особый геморрой


Что же, мы еще обнаружили и трудноуловимые ошибки. Опять же, в большинстве случаев требовался переход на новые версии библиотек или же их модификацию. Часто бывает, что при обновлении достаточно старой библиотеки, она меняет поведение элементов, и мы получаем полный швах в наших прекрасно написанных CSS-правилах.

Большинство библиотек только фиксировала свои проблемы, связанные с предупреждениями в React 15. И только самые последние их версии будут работать в React 16. Это утомительная работа по поиску и обнаружению ошибок.

Переход на ES6-классы означает, что вам теперь нужно осторожным и не менять атрибут props. Раньше было просто предупреждение в консоль об этом, теперь это падает с ошибкой “Cannot assign to read only property XXX of object ‘#’”. Если вы деструктурируете атрибут props, и пытаетесь изменить его свойства, эта ошибка вас ударит по рукам. Вместо этого используйте Object.assign или его синтаксический сахар:

const newProp = {...prop, propertyToOverride: overrideValue}

Также React 16 будет предупреждать о получении bolean-значения у onClick-метода. Это случится, если вы напишете

onClick={!disabled && this.handleOnClick}

Поэтому придется управлять поведением внутри назначенной функции.

И тут выходит на связь React Native


У нас ведь есть еще iOs приложение, которое делит stores, utils и некоторые action creators с веб-приложением. Переход на React 16 также смотивировал нас использовать последний релиз React Native, который зависит на альфа-версии React 16.

К сожалению, нам нужны специальны таймеры для видео, и у нас был форк React Native, и мы вернулись на версию 0.34. React 16 совместимость пришла в React Native только с версии 0.48.0, так что это время для обновления…

Это оказалось особенно болезненным. Наибольшей головной болью оказалось, что мы использовали React Native Webpack Server, который позволял повторно использовать код между нашими проектами. Так что мы пересмотрели взгляды на использование сборщика и стали переходить на Haul.

Haul держит нас по-прежнему во вселенной Webpack, но было не так просто его настроить на существующий проект. Рассказ об этом тянет на отдельную статью.

Мы раньше импортировали изображения для iOs с помощью webpack require(‘image!image_src’), но теперь нет возможности использовать его в новых версиях React Native. Это была изнурительная работа — нам требовалось перемещать изображения и менять способ доступа к ним. Даже после использования codemod, эта часть работы была проделана в основном вручную.

Также мы обнаружили множество устаревших классов, и сейчас мы советуем использовать костыль в виде react-native-deprecated-custom-components. Вам понадобится самая последняя версия, так как они недавно обновили пакет для React 16.

Берем в работу новые фичи


Самом лучшим нововведением для нас оказался новый ErrorBoundary. React 16 теперь прекращает рендер дерева в случае ошибки (что хорошо), и вы сможете отрендерить какое-то сообщение об ошибке. Дополнительно, ограничения дают хорошую возможность иметь отдельное место для логирования хорошо описанных ошибок с информацией о стеке вызовов и о компонентах.

Это запустилось!


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

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

Подробнее
Реклама
Комментарии 20
  • 0

    Тоже столкнулся с такой проблемой. Мне особенно нравится в 16-й версии, что в качестве рендера можно вернуть массив. Но не тут-то было. Одна из часто используемых библиотек, а именно react-bootstrap, не работает (не работала; сейчас не знаю) с новой версией React'a (проблема всплывает при использовании модальных окон)… Поэтому приходится пока что сидеть на последней 15-й версии.

    • +1

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

      • +4

        Все как раз-таки наоборот. Собственно смена движка рендера ничего не ломает. Но в новом релизе разработчики поменяли некоторые API методы, удалили те, что были явно задепрекейчены полгода назад. Для мажорного релиза это нормально.

        • 0

          У вас взаимоисключающие параграфы. Невозможность частичного перевода проекта на новую версию библиотеки — типичная детская болезнь.

        • 0

          Вы не правы, там проблемы только у тех, кто изначально использовал реакт неправильно или недокументированные методы, просто в 16 версии гайки подзакрутили и где он раньше просто предупреждал, теперь начал ругаться и падать, ну и вся какое старьё выпилили.


          Всё это было известно с релиза v15, который состоялся в апреле 2016, а сейчас конец 2017.

          • 0

            Ещё раз: проблема не в отсутствии обратной совместимости, что нормально для мажорной версии (16 раз за 4 года уже ломали апи или просто так счётчик накручивали?). Проблема в невозможности постепенного переезда. "Не правильные" и "недокументированные" методы использовались ведь не просто так, а решали конкретные задачи, которые без них, "правильными" и "документированными" методами было не решить. Те же "порталы", например.


            Ну а аргумент "мы вам пол года кидали варнинги — почему вы до сих пор не обновили код сторонних библиотек?!?" — вообще мимо кассы.

            • +2

              WAT? Похоже вы даже не в курсе о принципах версионирования реакта, ну да ладно. Во-первых, 16 реакт полностью совместим с 15, скорей всего и ниже, если вы правильно работали со state, не использовали приватных методов или недокументированных функций. Всё что описано в статье можно было не делать, а просто зашимить, вот и весь перезд, тут же человек наконец-то решил привести кодовую базу в порядок. Во-вторых 16 реакт это не просто новый рендер, это абсолютно новый реакт, но как я уже написал, совместимый с предыдущими версиями.


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

              Вот это детский сад, всегда есть выход, отличный от "за залезу я в кишки" и те же порталы легко реализовывались через простенький HOC.


              Наверно вы опять сейчас напишете, что вот «у меня в $mol такой проблемы нет»… конечно нет, его же никто не использует, да и версионирования нет.


              16 реакт это отличный пример, как полностью переписать огроменную либу, которую использую по всему миру и ничего не сломать, да не просто не сломать, а сделать лучше и даже меньше по размеру. Молодцы они.

              • –2
                16 реакт полностью совместим с 15

                Некоторые (в том числе "документированные") апи выпилили, некоторые даже шимов никаких не имеют (что-то там про профайлинг) — это называется "полностью совместим" в современном мире?


                те же порталы легко реализовывались через простенький HOC.

                Приведите код.


                «у меня в $mol такой проблемы нет»… конечно нет, его же никто не использует, да и версионирования нет.

                Всё проще — мы не ломаем апи. Хочешь сломать апи — пили новую либу, а не прикидывайся той же самой. Это позволяет иметь в проекте одновременно несколько версий одного модуля и постепенно переползать с одной на другую.


                16 реакт это отличный пример, как полностью переписать огроменную либу

                А preact — отличный пример, как переписать огроменную любу в миниатюрную либу. Как мне плавно мигрировать проект с React на Preact или наоборот?

                • +2

                  Не, я пас, дальше без меня, у нас разный React. Хотите доказать какой Реакт плохой, пишите статью, хотя читать я её уже вряд ли буду с таким уровнем аргументации. Всего хорошего.

        • 0
          Особенно классно тем, кто только начал изучать react по русскоязычным мануалам. Например, здесь и здесь createClass приводится как один из основных способов создания элемента. А ты такой скачиваешь последний react, а он тебе а вот нифига.
          Я лично после такого решил vue.js изучать.
          • 0
            Здесь вы смешиваете проблемы библиотеки и русскоязычного сообщества. Здесь вина не react, а быстротечности веб-технологий. То же самое будет и с vue. А может быть и есть уже сейчас.
            Когда недавно пытался гуглить что-то по jquery, даже на английском языке, находились ответы, где советовали использовать $('.class').bind(), хотя казалось бы, 2017 на дворе.
            У нас в магазинах до сих пор лежат бумажные книжки, где описывается как сделать border-radius с помощью версткой таблицами и гифками =)
            • 0
              Дело не в сообществе. Дело в том, что для довольно таки свежей версии мануала уже неактуальна версия реакта. В серьёзных фреймворках функционал сначала помечают как Deprecated, всячески сигнализируют об этом в процессе работы приложения, и только потом уже выпиливают через N релизов.
              А вот если открыть новость к релизу react 15 и вдучиво почитать, но мы не увидим ничего про deprecated createClass.
              • 0

                А если открыть новость к релизу 15.5, то там об этом написано.


                Также начиная с 15.5 появились и ворнинги при обращении к React.createClass. Как этого можно было их не заметить — я не понимаю. Разве что если сидеть на версии 15.0, а потом внезапно перепрыгнуть на 16, пропустив все предыдущие.

                • 0
                  Т.е. подождите, они выпустили 7 апреля 2017 версию 15.5, в которой пометили одну из основых функциональностей как deprecated. Потом выходит ещё одна минорная версия, потом, 26 сентября 2017 выходит мажорная версия, в которой этот функционал просто выпилили? Это вообще как?
                  Повторюсь, для мелких проектов, которые используют полтора инвалида, это может быть нормально. Но для таких крупных, как React это недопустимое(imho) поведение. Использование больших фреймворков в проектах отличается сильной интертностью. Полгода — это вообще не срок для deprecated функциональности.
                  • –1

                    Ну заведите им об этом issue или дайте знать по каким-нибудь другим каналам связи, что вам такая политика релизов и депрекейтов не подходит.


                    Большую часть коммьюнити, статус-кво вполне устраивает. Если у вас есть свой взгляд на этот вопрос, поделитесь им с разработчиками React.

            • 0

              А здесь — createClass нe приводится. То, что вам не повезло нагуглить актуальный перевод, ничего не говорит о качестве фреймворка как такового.

            • 0
              Вопрос не совсем по теме.
              Новый проект начал на create-react-app 16. Настроил proxy, как в было 15 — не работает.
              Погуглил и с особым цинизмом прописал в \node_modules\webpack-dev-server\lib>Server.js
              прямо в коде:
              options.proxy = {
              "/upload": { target: «127.0.0.1:8081», secure: false },
              "/download/": { target: «127.0.0.1:8081», secure: false},
              }
              Заработало, но осадочек остался.
              Просветите, плиз, в каком конфиге это задается.
              • 0
                В webpack.config.js

                devServer: {
                    proxy:{...}
                }
              • +3
                Детский сад это библиотеки которые мешают апгрейду, например react-datepicker все никак не смержит PR для поддержки React 16. Хотя их за месяцы до релиза просили это сделать.
                • 0

                  Хотел насладиться Fiber-ом. Накатил, ужаснулся количеству ошибок в зависимостях. Откатился взад. Рано!

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.