最近在用 React 以及 Redux 写几个项目, 使用的是官方 Create-React-App 的脚手架, 默认没有开启 HMR, 每次都要等他自动刷新浏览器效率非常低, 因此考虑使用 HMR 模式

Create-React-App 开启热替换

如果没有使用 Redux, 单纯使用官方脚手架的话其实很简单, index.js 里面加上这句就可以:

  if (module.hot) {
    module.hot.accept();
  }

Create-React-App + Redux 开启热替换

如果按照上方的方法,直接开启热替换的话, 可能出现 state 被重置的问题。 比如我 toggle 了某个控件, 修改代码热替换完毕之后, 需要重新 toggle 一次, 因为 toggle 之后的状态被重置了,这个很可能是因为所有的状态都被 reduxProvider 接管了。

不过其实也有办法。

首先要安装三个重要的库:

npm install react-app-rewired react-app-rewire-hot-loader react-hot-loader

根目录创建一个 config-overrides.js 文件, 注意是在根目录,而不是在 src 文件夹下面:

  const rewireReactHotLoader = require('react-app-rewire-hot-loader');

  module.exports = function override(config, env) {
    config = rewireReactHotLoader(config, env);
    return config;
  }

如果使用的是普通的 Create-React-App 脚手架, 那么就直接修改 index.js:

  import React from 'react';
  import ReactDOM from 'react-dom';
  import './index.css';
  import App from './App';
  import registerServiceWorker from './registerServiceWorker';

  // Add this import:
  import { AppContainer } from 'react-hot-loader';

  // Wrap the rendering in a function:
  const render = Component => {
    ReactDOM.render(
      // Wrap App inside AppContainer
      <AppContainer>
        <Component />
      </AppContainer>,
      document.getElementById('root')
    );
  };

  // Do this once
  registerServiceWorker();

  // Render once
  render(App);

  // Webpack Hot Module Replacement API
  if (module.hot) {
    module.hot.accept(() => {
      render(App);
    });
  }

另外需要修改一下 package.json:

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test":  "react-scripts test --env=jsdom"
  }

修改成:

  "scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test":  "react-app-rewired test --env=jsdom"
}

启动的时候会自动寻找根目录下面的 config-overrides.js 文件, 然后所有的设置都全部完成, npm start 体验一下 HMR 吧。

2020-1-28 更新: does not support changing store on the fly

does not support changing store on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reduxjs/react-redux/releases/tag/v2.0.0 for the migration instructions.

其实就是旧版本的 module.hot.accept 方法使用有一些变化.

以前的方法:

  if (module.hot) {
    module.hot.accept();
  }

现在需要显式调用:

// Webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept(() => {
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
    );
  });
}

这里是一个例子, 和文首提供的方法一样:

const store = createStore(
  Reducer, /* reducer */
  composeEnhancer(
    applyMiddleware(thunk), /* enhancer(middleware) */
  ),
);

const render = (Component) => {
  ReactDOM.render(
    <Provider store={store}>
      <Component />
    </Provider>,
    document.getElementById('root'),
  );
};

// Do this once
registerServiceWorker();

render(App);

// Webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept(() => {
    render(App);
  });
}

参考文献