Эта статья написана в 2017 году. Время лечит раны:



^ это статья из 2018 года ^

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Итак, вы только что настроили HMR (если нет - переходите к части 1), и думаете - дальше. Как использовать горячую перезагрузку в случае React.

Это таааааааааааааааааааааааааачеадочка! Вы просто редактируете свой код в редакторе и ВАУ! были исправлены горячие исправления, и вы видите изменения на лету.

Итак, все, что я знал о React Hot Code Reloading раньше - это просто работает. И с легкостью!

Как я впервые оцениваю задачу «включить HMR»?

3 строки кода. 0,5 очка истории.

При этом оценка была почти верной. Почти.

Если вы читали документацию, следовали руководствам, но ничего не работает так, как ожидалось - добро пожаловать в клуб!

Шаг 1 - React Hot Loader

Итак, сразу после настройки HMR вам нужно добавить в проект одну новую зависимость, чтобы преобразовать HMR с уровня module на уровень component.

Реагировать-горячий-загрузчик. Версия 3.

Итак, как написано в руководстве по миграции:

  1. добавить новый плагин в конфигурацию babel - react-hot-loader / babel
  2. добавить новый файл перед основными файлами - react-hot-loader / patch
  3. оберните ваше приложение AppContainer.

Это все!

Плагин Babel prepare ваш код для React-HMR. Патч действительно patch Реагирует на поддержку горячей замены. И AppContainer заставит redraw вашего приложения.

Шаг 2 - Кричите.

Для простого приложения - это сработает. И, в любом случае, в 99,9% случаев он сохранит состояние редукции.

Но если вы «крутой программист», следуете лучшим практикам и используете множество простых для чтения функций, декораторов и т. Д. -

Это НЕ сработает.

Просто потому, что есть несколько ограничений ...

  1. Patch использует React-Proxy для обертывания ваших компонентов с помощью Proxy и имеет возможность replace component без замены компонента.
  2. Плагин Babel sees только переменные верхнего уровня (загрузчик webpack видит только экспорт).
  3. Да, пока HoC, функционал chains и compositions используют временные переменные - они облажались.
  4. Нет отладочной информации.

Шаг 3 - Понимание проблемы.

Вы должны понять простую вещь - нет Санты и нет Magic внутри React-Hot-Loadable.

  1. Плагин Babel ищет объявление компонента (все переменные, классы и функции TOP LEVEL). Затем он добавляет новый код в конец файла для вызова REACT_HOT_LOADER.register (class, exportedName, location) для каждого найденного something.
  2. Затем REACT_HOT_LOADER сохраняет class со ссылкой на местоположение в некоторой WeakMap.
  3. Если в один прекрасный день слот будет занят - значит, вы выполняете 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.

  1. Патч перегрузки React`s createElement
  2. На элементе создайте его, ищите type внутри зарегистрированных компонентов.
  3. Если он присутствует - Патч вернет Прокси. Как результат - 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.

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

Если у вас есть HoC с connect внутри - переходите к шагу №1. Другого выбора нет.

Очень осторожно в обоих случаях. В react-hot-loadable нет встроенной отладки.

Вы можете отлаживать свой код с помощью предупреждений. Почему нет?

Шаг 7 - отладка

В порядке. Есть способ отладки БРЗ. Я собираюсь открыть пиар по этому поводу, а пока вы можете немного поправить БРЗ.

  1. Откройте /node_modules/react-hot-loader/lib/patch.dev.js.
  2. найти функцию класса разрешения.
  3. Патч это
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.