Эта статья написана в 2017 году. Время лечит раны:
^ это статья из 2018 года ^
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Итак, вы только что настроили HMR (если нет - переходите к части 1), и думаете - дальше. Как использовать горячую перезагрузку в случае React.
Это таааааааааааааааааааааааааачеадочка! Вы просто редактируете свой код в редакторе и ВАУ! были исправлены горячие исправления, и вы видите изменения на лету.

Итак, все, что я знал о React Hot Code Reloading раньше - это просто работает. И с легкостью!
Как я впервые оцениваю задачу «включить HMR»?
3 строки кода. 0,5 очка истории.
При этом оценка была почти верной. Почти.
Если вы читали документацию, следовали руководствам, но ничего не работает так, как ожидалось - добро пожаловать в клуб!
Шаг 1 - React Hot Loader
Итак, сразу после настройки HMR вам нужно добавить в проект одну новую зависимость, чтобы преобразовать HMR с уровня module на уровень component.
Реагировать-горячий-загрузчик. Версия 3.
Итак, как написано в руководстве по миграции:
- добавить новый плагин в конфигурацию babel - react-hot-loader / babel
- добавить новый файл перед основными файлами - react-hot-loader / patch
- оберните ваше приложение AppContainer.
Это все!
Плагин Babel prepare ваш код для React-HMR. Патч действительно patch Реагирует на поддержку горячей замены. И AppContainer заставит redraw вашего приложения.
Шаг 2 - Кричите.
Для простого приложения - это сработает. И, в любом случае, в 99,9% случаев он сохранит состояние редукции.
Но если вы «крутой программист», следуете лучшим практикам и используете множество простых для чтения функций, декораторов и т. Д. -
Это НЕ сработает.
Просто потому, что есть несколько ограничений ...
Patchиспользует React-Proxy для обертывания ваших компонентов с помощью Proxy и имеет возможностьreplace componentбез замены компонента.- Плагин Babel
seesтолько переменные верхнего уровня (загрузчик webpack видит только экспорт). - Да, пока HoC, функционал
chainsиcompositionsиспользуют временные переменные - они облажались. - Нет отладочной информации.

Шаг 3 - Понимание проблемы.
Вы должны понять простую вещь - нет Санты и нет Magic внутри React-Hot-Loadable.
- Плагин Babel ищет объявление компонента (все переменные, классы и функции TOP LEVEL). Затем он добавляет новый код в конец файла для вызова REACT_HOT_LOADER.register (class, exportedName, location) для каждого найденного
something. - Затем REACT_HOT_LOADER сохраняет
classсо ссылкой на местоположение в некоторой WeakMap. - Если в один прекрасный день слот будет занят - значит, вы выполняете
hot updateи регистрируете что-тоnewизoldместоположения. Иoldэкземпляр должен бытьreplacedнаnew.
В этот момент происходит собственно HRM. Наконец.
В порядке. Напишем простой код
const superHoC = (something) => (Component) => (
<div>blabla <Component with={something} /></div>
);
const MySuperApplication = connect(mapStateToProps)(superHoC(withSomethingElse(SuperApplication)));
Сколько компонентов вы (и Вавилон) видите здесь?
- Один компонент и один компонент более высокого порядка.
Сколько их на самом деле?
- [SuperApplication], withSomethingElse (SuperApplication), superHoC (withSomethingElse (SuperApplication)) и соединение (withSomethingElse (withSomethingElse (SuperApplication))).
Не менее 3. И 2 из них будут не основаны плагином babel, не будут зарегистрированы для Patch, не будут оберткой с реактивным прокси и будут рассматриваться как новые компоненты в событии HMR. В результате:
Вызову перемонтировку. Вы потеряете внутреннее состояние.
Шаг 3 - фиксация.
Все, что вам нужно сделать, это разбить functional chain на отдельные переменные
const superHoC = (something) => (Component) => (
<div>blabla <Component with={something} /></div>
);
// each intermediate class as a separate variable.
const WithSomethingElse = withSomethingElse(SuperApplication);
const WithSuperHoc = superHoC(WithSomethingElse)
const MySuperApplication = connect(mapStateToProps)(WithSuperHoC);
Так что забудьте о modern функциональном стиле, также известном как композиции. В этом простом случае плагин babel увидит все части и сможет обновить все, что он должен обновить, при событии HMR.
PS: Честно говоря - это правило решает практически все проблемы, в том числе и перечисленные ниже….
Так что правило глупое простое
Все части компонента должны быть
visibleк надстройке babel. Никаких цепочек функций, никаких декораторов. Только переменные.
Но, пожалуйста, прочтите это правило еще раз ... И вспомните, как вы обычно пишете компоненты более высокого порядка. Давайте посмотрим на руководство Facebook:
function logProps(WrappedComponent) {
return class InternalComponent extends React.Component {
componentWillReceiveProps(nextProps) {
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
}
const FinalComponent = logProps(MyComponent); // will work
const Component1 = connect(..)(logProps(MyComponent));// will not
const Component2 = connect(..)(FinalComponent);// will work
Этот пример будет работать для первого случая, пока plugin будет видеть FinalComponent.
Этот пример будет работать для третьего случая, пока plugin будет видеть и FinalComponent, и Component2 (соединение).
Но во втором случае это не сработает - я нигде не хранил темпоральный класс.
Но это простой случай.
function logProps(WrappedComponent) {
class InternalComponent extends React.Component {
componentWillReceiveProps(nextProps) {
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
const FinalClass = connect(mapStateToProps)(InternalComponent);
return FinalClass;
}
В этом случае вы МОЖЕТЕ иметь logProps как registered компонент, но InternalComponent будет скрыт внутри. И Прокси не поймет, что вы делаете, - в результате будет перемонтировано все вложенное дерево.
Перемонтирован? В двух словах - у вас будет
lose an internal stateкомпонентов. И активируйте размонтирование / монтирование на всем дереве вложенности. Не то, что тебе нужно.
Например, он сбросит redux-form.

Шаг 4 - Прокси
Последнее, что вам нужно понять - это то, как работают Proxy и Patch. И почему вам нужно использовать AppContainer.
- Патч перегрузки React`s
createElement - На элементе создайте его, ищите
typeвнутри зарегистрированных компонентов. - Если он присутствует - Патч вернет Прокси. Как результат -
softlyоборачивает старый компонент новым.
Прокси - это грязный хакер. Если вы обновите прокси - вы обновите только прокси. Затем вам нужно запустить обновление. Вынужденный.
В некоторых случаях компонент React, завернутый в React-Proxy, не реагирует на изменение свойств. Cos
props- это внутреннее состояние компонента, иtypeне изменяется.
Следующая очень важная вещь - эта перерисовка вызовет только перерисовку. Вы можете применить хот-патч только к методам рендеринга.
Только учтите - мы боремся с ремонтами. Когда мы заподозрили - ничего монтировать / демонтировать не будет. Половина вашего кода не будет выполняться «горячим» образом.
Это сложно объяснить, просто заверните все в AppContainer. И не забывайте время от времени обновлять всю страницу.
Единственный случай для React Hot Loader - это тестировать небольшие патчи. Больше ничего.
И единственное, что вам нужно понять - Патч использует React.createElement.
Шаг 5 - Компоненты более высокого порядка
Итак, чтобы ваши компоненты высокого порядка работали, вам нужно использовать createElement, а не создавать новый класс.
class InternalComponent extends React.Component {
componentWillReceiveProps(nextProps) {
}
render() {
const { WrapperComponent, props } = this.props;
return <WrappedComponent {props} />;
}
}
const ConnectedComponent = <-- is visible to babel
connect(mapStateToProps)(InternalComponent); <-- is visible to babel
function logProps(WrappedComponent) {
return (props) => <InternalComponent wrapped={WrappedComponent} props={props}/> <-- only creates a known component.
}
В этом случае вы не create новый класс, вы используете другой с настраиваемыми реквизитами.
В результате у вас будет не ОДИН smart HoC, а ДВА субкомпонента. И оба должны быть видны плагину.
Шаг 6 - легкий способ.
В конце концов, вы должны иметь в виду 2 вещи, чтобы использовать hi-order-components с react-hot-loader.
- тщательно проектируйте свои компоненты, как я делаю на шаге 5.
- тщательно извлеките каждую часть вашего компонента в отдельные переменные, как я делаю на шаге 3. Без внутренних промежуточных компонентов.
Если у вас есть HoC с
connectвнутри - переходите к шагу №1. Другого выбора нет.
Очень осторожно в обоих случаях. В react-hot-loadable нет встроенной отладки.
Вы можете отлаживать свой код с помощью предупреждений. Почему нет?

Шаг 7 - отладка
В порядке. Есть способ отладки БРЗ. Я собираюсь открыть пиар по этому поводу, а пока вы можете немного поправить БРЗ.
- Откройте /node_modules/react-hot-loader/lib/patch.dev.js.
- найти функцию класса разрешения.
- Патч это
function resolveType(type) {
// We only care about composite components
if (typeof type !== 'function') {
return type;
}
hasCreatedElementsByType.set(type, true);
// When available, give proxy class to React instead of the real class.
var id = idsByType.get(type);
if (!id) {
if (type) {
if (!visitedClasses.get(type)) {
// ADD THIS LINE
console.log('[RHL] unknow class', type); <--- imposter!
}
visitedClasses.set(type, true)
}
return type;
}
var proxy = proxiesByID[id];
if (!proxy) {
return type;
}
return proxy.get();
}
И вы получите сообщение об ошибке, если патч столкнется с новым компонентом.
В обычных ситуациях вы увидите много сообщений о запуске, если все компоненты из узловых модулей не registered. Но при перезагрузке - ничего не должно быть видно.
Проверьте свои журналы и раскопайте свой код. Смотри удачи.
Шаг 8 - ОТКЛЮЧИТЕ !!!
При горячей загрузке react-hot-loader только повторно визуализирует компоненты. Также AppContainer заставит все повторно выполнить рендеринг.
AppContainer даже повторно визуализирует чистые компоненты. Без смены реквизита.
Как следствие - поведение приложения может отличаться от реального.
Используйте HRM только для разработчиков. Не забудьте проверить, как ваше приложение работает без него.
PS: А что насчет разбиения кода?
Итак - вы используете лучшие практики и используете разделение кода в своем проекте.
Так будет HRM работать с отложенными порциями… Итак, HRM будет работать, но React-Hot-Loader…

Проблема простая. Как всегда.
HMR будет запускаться обновлением фрагмента, если вы use этот фрагмент - вы его родитель.
Следующий AppContainer принудительно перерисовывает все компоненты.
Перерисовать. Не перемонтировать!
А инструмент, который вы используете для загрузки отложенного фрагмента - react-loadable, или react-universal-component, - будет просто render existing component. Пока они рассчитаны на загрузку только на крепление. Нет крепления.
Ты можешь починить это? Неа. Нет возможности исправить существующие компоненты.
Запишем новый.
Короче - я его назвал react-hot-component-loader.
Вопрос в том, ПОЧЕМУ это работает. Все благодаря этому коду
componentWillReceiveProps() {
// Hot reload is happening.
if (module.hot) {
this.remount();
}
}
remount() {
this.loader()
.then((payload) => {
this.setState({AsyncComponent: payload});
});
}
Этот код перехватит событие HMR и заменит внутренний AsyncComponent без перехода в состояние loading.
Без переустановки дерева гнездования.
Но сразу после того, как вы загрузили (наконец-то) новую версию компонента, и будьте готовы увидеть новую версию… Так что ничего не произойдет :)
Перерисовка hot-replaced компонента должна быть driven на AppContainer. Но он сделал свое дело и заснул.
Но решение также простое - оберните ваш AsyncComponent AppContainer.
if (AsyncComponent) {
return (
<AppContainer>
<AsyncComponent {...this.props} />
</AppContainer>
);
}
Итак - вы можете найти response-hot-component-loader на github.
PS: react-universal-component может обрабатывать HRM, но я не уверен, сможет ли он справиться с React-Hot-Loader.
Вывод
Горячая (пере) загрузка - это потрясающе! Это поможет вам применить изменения и увидеть их в той же среде.
Но если вы не можете сохранить состояние на событии HMR - лучше не на HMR.
Сохранить хранилище redux очень легко, но сохранить внутренние react состояния - намного, намного сложнее. Иногда.
Если вы используете MobX, i18n, функциональные композиции, пакеты HoC или что-то еще - React Hot Loadable не будет работать должным образом. А иногда - fix это невозможно.
Тогда я впервые встретил React Hot Loader (это было 2 (два) дня назад), надеюсь, все будет достаточно просто. По телевизору. Ха!
К концу дня меня арестовали. Я теряю доверие к webpack, к реакции, к людям.
Я прочитал много статей, разбираюсь в источниках, нахожу возможные пути…
И к концу второго дня напишите эту статью: P
Это удивительно, но нет easy описания, почему RHL работает так, как работает, и как change your code соответствовать ожиданиям этой библиотеки.
Еще раз - вам нужно изменить свой собственный код.
Или разработайте более удобный плагин babel или даже расширение для браузера.
Вот и все, надеюсь, вы найдете несколько глупых ответов на несколько глупых вопросов. Но если нет, или если они вам не помогут - забудьте о HMR, живите сладким и без него.
PS: Почему я назвал эту тему как «За 7 дней», а все сделал за 2?
Это не конец.
Состояние настроено.
PS: через 5 дней я добавил статью с кейсом AsyncComponentLoader.