Горячая перезагрузка компонентов в React

    У меня есть два любимых запроса в гугле:

    — Что будет, если в унитаз поезда на полном ходу бросить лом?
    — Что будет, если в реакту по полном ходу заменить компонент?

    И если с первым вопросом все более менее понятно, то со вторым вопросом все не так просто — тут же вылезает Webpack-dev-server, Hot Module Replacement, и React-Hot-Loader.

    И все бы хорошо, но эта гремучая смесь просто не работает, спотыкаясь на первом же сложном коде, HOC, композиции, декораторе и нативных arrow функциях.

    В общем третья версия Reac-hot-loader заводилась далеко не у всех.



    Преамбула


    На самом деле у версии 3 было 3 проблемы:

    — react-proxy, который использовался для «подмены» старой версии компонента новой, и который совсем не дружил с arrow функциями, к которым контекст прибит намертво.
    — для обеспечения работы bound и transpiled arrow functions babel plugin оборачивал их в промежуточные функции. С учетом того, что он это делал для ВСЕХ классов — часто это приводило к поломкам. Те вообще все не работало.
    — сам принцип поиска и подмены компонентов. Для этого использовался babel plugin, который «регистрировал» все переменные верхнего уровня, таким образом что потом можно было бы понять, что «вот эта переменная», на самом деле «БольшаяКпонка» и следует обновить прокси.

    Все бы хорошо, но HOC может создать класс внутри себя, а декоратор просто «декорирует» реальную переменую. Как результат — реакт-реконсилер при рендере встретит «совершенно новый» класс на месте старого и размаутит все старое дерево.

    Народная мудрость гласит:
    One of the most annoying things to setup with Webpack is a properly working Hot Module Replacement. Working properly means that components will keep their local state. Everything else makes HRM uselss (in my opinion).

    Другими словами — без нормального preserve state нафиг этот RHL не нужен.

    Мое путешествие в RHL началось полгода назад, когда я примерно неделю (это очень много) пытался понять как все эти годы люди использовали React-hot-loader, потому что использовать его невыносимо больно. Ничего кроме боли он не приносит. А потом открыл PR, который молча стал подсказывать почему и где RHL размаунтит компонент, чтобы люди наконец смогли понять почему он не работает. Чтобы хоть как-то сгладить душевные страдания.

    А потом решил починить все на корню.

    Встречаем версию 4!


    Буду краток — версия 4 работает практически всегда. У нее все еще есть ограничения, которые вытекают из самой природы явления, но эти «ошибки» — гарант правильности работы. Пробовать можно уже сейчас, просто обновите RHL до версии 4.

    Что такое HMR, RHL и все такое?


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

    RHL — react-hot-loader, комплекс мер которые направлены на то, чтобы React просто перерендерил обновленный элемент, и не делал больше ничего.

    RHL и есть то нечто, что обеспечивает «правильный» HMR в том виде, в каком он и нужен.
    Есть альтернативная теория, гласящая что HMR ведет к monkey-patchингу, в лучше TDD, BDD и все такое. И большая такая капля правды в ней есть.

    Отличия версии 4 от версии 3


    1. Вместо react-proxy используется react-stand-in — чуть более «javascriptовое» решение. Основное отличие — standin это не «враппер», и не прокси — это класс который наследуется от реального.
    Это очень хитрый момент — когда настанет время заменить один компонент другим — standin просто поменяет себе прототип (точнее прототип своего прототипа).
    Результат — this никогда не меняется.

    Второй хитрый момент — перенос изменений сделанных в конструкторе (в том числе добавленных в конструктор бабелем). Standin инстанцирует старый и новый класс, после чего ищет изменения и пробует их повторить.

    В итоге — после обновления компоненты применяются все изменения, кроме сделанных в componentWill/DidMount. Потому что mount/unmount не происходил, и именно его и требовалось избежать.

    2. Вместо «регистраций», которые не дружат с HOC и декораторами используется механизм названный «hotReplacementRender».

    Когда наступает момент «обновления» компоненты React-Hot-Loader сохраняет старое дерево, и начинает сам рендерить новое.

    После рендера каждого элемента наступает момент «сравнения» старых и новых данных, фактически реконсилер.
    Если новый компонент «очень похож» на старый — возможно это он и есть, и можно произвести замену.

    Такой подход без проблем пробивает любые композиции, декораторы, и вообще любые render-props. Единственный момент, который не переживает — изменение колличества или порядка следования детей (если не заданы key), но это вытекает уже из самой природы Reactа.

    3. Раньше многие люди спотыкались на моменте настройки HRM и RHL. В новой версии вся настройка ограничивается _одной_ командой.

    import React from 'react';
    import {hot} from 'react-hot-loader';
    
    const MySuperApplication = () => ......
    
    export default hot(module)(MySuperApplication); <-----
    

    Магия hot(module)(MySuperApplication) автоматически настроит HMR для текущего модуля, а HOC часть этой функции обернет MySuperApplication в AppContainer.

    ВСЕ! (Плюс babel-plugin не забудьте)

    Где работает? Везде — Webpack, parcel, typescript(babel-plugin не забудьте). Примеры есть на все.

    Особенности работы, про которые лучше знать


    1. RHL v3 оборачивал в прокси только «зарегистрированные» компоненты. v4 оборачивает абсолютно все. Теоретически это никак не должно сказываться на производительности, но всякое бывает.

    2. Hot настраивает текущий модуль как self accepted. Тут надо понимать что ничего кроме «реакт-компоненты», который RHL может переживать экспортировать из модуля нельзя.

    Стандарные сейчас ошибки:

    — использовать hot для локальных переменных или вообще в рендере (детектим и ругаемся)
    — использовать hot для HOC и функций — молча все ломается.

    Hot — _только_ для компонент.

    3. Если в componentDidMount вы запустили таймер, а потом изменили функцию которую этот таймер вызывает — она не обновиться. Потому что setTimeout уже получил ссылку на функцию, и поменять ее нельзя.

    4. Code splitting — требуется или обернуть в hot каждый компонент который вы собираетесь динамически загружать, тогда он «сам себя» обновит, или использовать «загрузчик» который знаком с HMR/RHL.

    — популярный react-lodable в принципе не подходит. Если он у вас — никаких других вариантов, крое как обернуть компоненты в hot — нет. (не забываем что это никак не сказывается на продакшене)

    loadable-components немного лучше — они попытаются обновить файл, но могут быть проблемы, так как «hotReplacementRender» будет уже выключен когда сработает import.

    — 100% хорошо работает react-imported-component, так как оборачивает обображаемый компонент в AppContainer, и это «спасает» ситуацию, но его SSR немного специфичен, и подойдет не всем.

    5. Я думаю это далеко не конец.

    React-hot-loader это не серебрянная пуля — а достаточно узкоспециализированный инструмент, который может быть очень полезен. А может и не быть.

    Время попробовать?


    Прямо сейчас мы близки к выпуску RC версии. Банально больше нет багов чинить и фич добавлять. Только немного code coverage повышать.

    Сейчас самое то время установить RHL себе, и попробовать его в деле. И сообщить о всех проблемах что возникнуть (конечно же!) не должны :D

    В общем — огонь!
    github.com/gaearon/react-hot-loader/tree/next
    npm install react-hot-loader@next

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 18

      0
      Картинка отлично показывает третью версию.

      Лично для меня react-hot-loader «умер» когда обнаружилось, что теперь надо оборачивать все компоненты их кодом. Я понимаю зачем это нужно, но вот это совсем неудобно. К тому же настройка 3-й версии, мягко говоря, неочевидная (по крайней мере была, когда та только вышла). В итоге отказался от него совсем :(

      А что присходит в проде при такой hot() обертке? Оно безболезненно вырезается?
        0
        Конечно. В проде от hot-loader не остается ничего.

        А насчет оборачивая «их кодом» — имхо — это супер минор.
        0

        Фича ради фичи.


        • работает не для всего (только компоненты)
        • работает не всегда (изменения cW/DM)
        • а порой и не работает (для HOC молча падает)

        Ради чего все эти ограничения и работа с кодом, который даже близко не похож на то, что будет на проде? Ради экономии одного нажатия F5? И всё это вместо того, чтобы:


        • нормально запроектировать приложение, чтобы F5 восстанавливал его в том же состоянии
        • сделать приложение лёгким и быстро запускающимся.
          +1
          — работает не для всего (только компоненты)
          V3 работал только для компонентов, доступных как переменные. V4 работает на уровне отрендереного React-дерева, в котором НЕ компонент не бывает. А вот то, что hot требует на вход компоненту — как раз логично. Это же базовый строительный блок. И обернуть достаточно «Application»(точку входа). Которая компонента по умолчанию.

          — работает не всегда (изменения cW/DM)
          К сожалению сложно опять что компонент немного по другому должен старотовать, так как изменение может находиться вне пределов функции. RHL ругнется в консоль, если заметит что-то странное, но обновлять ничего не будет.

          — а порой и не работает (для HOC молча падает)
          Кто?

          — Ради экономии одного нажатия F5?
          F5 вы можете сами нажать, а вот для того чтобы привести все приложение в старый стейт — потребуется 5-20 секунд.

          — чтобы F5 восстанавливал его в том же состоянии
          В общем случае это или не возможно (половине UI сложно прокинуть стейт из redux/mobx), или не нужно (половина элементов UI работают через свои стейты).

          Как уже говорилась — версия 4 работает практически для любого приложения. Единственный способ «сломать» — добавить новую ноду перед старыми — следуя дефолтному поведению React все перемаунтит. Решили не бороться со своей стороны.

          Некоторые люди считают, что RHL — «вреден». Имхо он позволяет использовать реальный IDE заместо браузерной консоли, чтобы поправить стили или поведение элементов, тем самым позволяя быстрее создать более приятный пользователю look-n-feel.
          Тесты — очень вредное явление. Главное потыкать мышкой и убедиться что приложение для пользователя удобно и приятно. То что согластно тестам оно работает правильно — для пользователя не значит ровным счетом ничего.
            0
            Я правильно понимаю что если у меня React Router Redux — то нет шансов заставить его работать? Или есть примеры как решать подобную проблему? Сейчас приложение вызывает componentWill/DidMount каждый раз в режиме RHL. ЧЯДНТ?
              0
              V4/next? Создай issue, дай сорсы — разберем.
                0
                Да V4/next. Как раз почитал статью и перевел проект на него надеясь включить hot reload. Постараюсь в свободное время отловить эту тему и написать тестовый example-проект для пруфа.
                  0
                  Не забывайте про
                  import { setConfig } from 'react-hot-loader'
                  setConfig({ logLevel: 'debug' })
            0
            Ради экономии одного нажатия F5?

            еще экономит нажатия cmd+r.
            0
            Умоляю, замените «заместо» на «вместо». Отличная статья, но это режет глаз
              +2
              Только что применил hot-patch к статье, заменив <Заместо /> на <Вместо />
              +1
              А что будет если в унитаз поезда на полном ходу бросить лом?
                +1
                Да, у RHL есть недостатки. Однако про плюсы никто не говорит. «Верстать» компоненты намного приятнее, ибо на каждый чих страница не перезагружается. И бóльшая часть времени разработки в React — это композиция компонентов. Я бы сказал, процентов 90 времени.
                Так же, многое зависит от того, как выстроена архитектура самого приложения.

                TL;DR: очень сомнительные вайны (местами приемлимые). Вместо нытья — поговорите на Github с мэинтейнерами и откройте пулл реквесты.
                  0
                  Именно так и я поступил — в начале поныл, а потом написал полностью новую версию, с которой ныть уже не надо.
                  +1

                  вместо того чтобы мучаться с настройкой RHL, я стараюсь сохранить состояние страницы в URL, localStorage или еще как-то. Чтобы при Crtl+R страница открывалась в том же виде что и до.


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

                    0
                    > Чтобы при Сtrl+R страница открывалась в том же виде что и до.
                    Вот есть у вас форма — пользователь ввел в нее данные и (!)обновил страницу.
                    — пользователь видит форму какой она была — пользователь очень рад.
                    — пользователь видит форму такой какой она _должна_ быть (со старыми данными) — пользователь не рад.
                    И оба этих варианта имеют право на жизнь. А то тот же самый пользователь не сохранит данные, потому что и так же все нормально.
                    А где есть два варианта — там есть и три.
                      0

                      Это сильно зависит от формы. Если это большая и сложная форма регистрации, то пользователь будет очень рад, что ему не нужно проходить все шаги заново.


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


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

                        0
                        В общем надо разработать философию — «зачем нужен RHL», и так чтобы адептам TDD продать можно было.

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

                  Самое читаемое