如何使用React Fast Refresh

react-refresh是react官方实现的热替换方案,用于替换其他的方案,如react-hot-loader等。

在项目中使用react-refresh(webpack)

在babel配置中,需要添加babel插件react-refresh/babel
添加全局代码。
// global
if (
  process.env.NODE_ENV !== 'production'
  && typeof window !== 'undefined'
  && !window.$RefreshInstall$
  && module.hot
) {
  const ReactRefresh = require('react-refresh/runtime');
  
  ReactRefresh.injectIntoGlobalHook(window);
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => (type) => type;
  window.$RefreshTime$ = null;
  window.$RefreshInstall$ = true;
}
在组件代码的头和尾添加代码。
/* 代码头部添加 */
let __$prevRefreshReg$__;
let __$prevRefreshSig$__;

if (
  process.env.NODE_ENV !== 'production'
  && typeof window !== 'undefined'
  && module.hot
) {
  __$prevRefreshReg$__ = window.$RefreshReg$;
  __$prevRefreshSig$__ = window.$RefreshSig$;

  const ReactRefresh = require('react-refresh/runtime');
       
  window.$RefreshReg$ = (type, id) => {
    const fullId = module.id + ' ' + id;
                
    ReactRefresh.register(type, fullId);
  }

  window.$RefreshSig$ = ReactRefresh.createSignatureFunctionForTransform;
}

// ====================
// Your Code
// ====================

/* 代码尾部添加 */
if (
  process.env.NODE_ENV !== 'production'
  && typeof window !== 'undefined'
  && module.hot
) {
  const ReactRefresh = require('react-refresh/runtime');

  window.$RefreshReg$ = __$prevRefreshReg$__;
  window.$RefreshSig$ = __$prevRefreshSig$__;
  module.hot.accept();

  if (window.$RefreshTime$ === null) {
    window.$RefreshTime$ = setTimeout(() => {
      window.$RefreshTime$ = null;
      ReactRefresh.performReactRefresh();
    }, 30);
  }
}
这段代码必须在被react-refresh/babel编译后的代码之前,否则不生效。
所以在webpack中,可以通过开发loader,来添加这段代码。
function ReactRefreshLoader(source, sourcemap) {
  const callback = this.async();
  let sourceData = source;

  if (source.includes('= $RefreshSig$()')) {
    sourceData = `${ ReactRefreshPrev }\n\n${ source }\n\n${ ReactRefreshEnd }`;
  }

  callback(null, sourceData, sourcemap);
}

module.exports = ReactRefreshLoader;
也可以通过string-replace-loader来注入这段代码。
module.exports = {
  module: {
    rules: [
      {
        test: /^.*\.jsx?$/i,
        use: [
          {
            loader: 'string-replace-loader',
            options: {
              search: /^(\s|.)*$/,
              replace(match) {
                if (match.includes('= $RefreshSig$()')) {
                  return `${ ReactRefreshPrev }\n\n${ match }\n\n${ ReactRefreshEnd }`;
                }

                return match;
              }
            }
          },
          {
            loader: 'babel-loader',
            options: {
              presets: [
                ['@babel/preset-react', {
                  runtime: 'automatic',
                  development: true
                }]
              ],
              plugins: ['react-refresh/babel'],
              exclude: /node_modules/
            }
          }
        ]
      }
    ]
  }
};