大家好,我是海怪。
最近发现接手的项目里一直有用 react-hot-loader 这个库,然后我就想:Webpack 不是已经有了一个 HMR(Hot Module Replacement) 的玩意了么?还要这个 loader 干啥的?
然后搜到了这篇文章:Blogged Answers: Webpack HMR vs React-Hot-Loader
这是一篇关于 HMR 和 react-hot-loader 的对比总结,里面讲得还比较清楚,下面给大家分享一下。
前言
很多人在构建 React 热更新的时候经常被 Webpack 的 HMR 搞蒙逼。一个经常容易把人搞蒙的点就是:以为要用 react-hot-loader
来打开 HMR,然而事实并不是这样,下面我就来对比一下他们的不同点。
HMR
Hot Module Replacement(HMR) 其实是 Webpack 自带的功能,通过
module.hot.accept
来实现。它的原理是:当项目里的文件被重新编译的时候,在 HMR 注册的一回调就会被执行:
- 除了项目里的入口文件,你要把 HMR client 代码也要作为入口文件。当然,这部分的 client 代码占很小一块,它只是负责打开 WebSocket,并连接上 Webpack dev server
- 入口调用
module.hot.accept("./someFileName", callbackToRunWhenThatFileIsRecompiled)
- Webpack dev server 会自动监听文件变化,当保存的时候自动重新编译
- 重新编译后,发消息通知 client,告诉它哪些文件重新编译了,以及最新版本的代码
- client 会根据重新编译的文件路径来执行
module.hot.accept()
的 回调函数
关于最后一点的 回调函数 要如何实现完全在于你自己。不过,一般的实现方式都是重新 import 变更的文件,并更换掉变更的代码部分,比如 React 组件、Redux 的 reducer 之类的。
如果你只要用这种 "plain HMR" 的话,只需要写一两行的 module.hot.accept()
就完事了:一个用来更新整个 React 根组件,另一个用来更新根 Reducer 文件。
可以看到,上面并没有使用 react-hot-loader
这个玩意!!只需要 Webpack 就可以实现了。
react-hot-loader
react-hot-loader
也是使用了 Webpack 的 HMR API,但是在实现方式上更复杂和强大!
RHL(react-hot-laoder) 会用一个 "proxy" Component(代理组件)包裹你的组件代码。这些 Proxy Component 会更细粒度地调用 module.hot.accept()
API 来抓取每个组件的更新,而不会让这些更新 “冒泡” 到根组件。
同时,这些 Proxy Component 还会拥有自己的状态管理机制,来管理被包裹的真实 React 组件。所以,当你代码改了之后,组件的状态还是会保留下来,而不会重新 “刷新”。
文章总结
RHL 还是挺好的...当它没报错的时候。但是,热更新这样的使用场景有太多的边界 case 了,RHL 也不可能囊括这么多 case,所以在使用的时候也会出现很多问题。
比如更改构建配置就可能使得 RHL 不能正常工作。这也是为什么 Dan Abramov 不再继续去搞 RHL,而是在 Create-React-App 里提供一个更稳定、持续、公开的配置环境作为基线,方便之后实现更“聪明”的热更新机制。
我自己来说还是不推荐使用 RHL,而使用原生的 "plain HMR" 就好了。虽然使用 "plain HMR" 在热更新的时候不会保留原来组件的状态,但是它的工作方式更简单粗暴,没那么多花里胡哨的东西。当然 Redux 也对 "plain HMR" 非常友好的,因为在热更新的时候 Redux 的状态一直都会在那,所以 React 组件在重新渲染的时候还是可以使用上次的 Redux 状态。
我的想法
当然上面这篇文章是 17 年写的,现在 Webpack 已经将 module.hot.accept
内置到配置中了,使用上也不用自己调用 API 了。而且 react-hot-loader
也发展了比较久了,很多问题也解决了不少。
个人觉得如果 react-hot-loader
还是值得一试的,如果没有太多问题的情况下。。实在不行就用原生的 HMR 喽。
不过目前发现 react-router
的 v6.x 版本和 react-hot-loader
不太兼容。刚不是说 react-hot-loader
会包一层 Proxy 组件么?在使用
<Routes> <Route></Route> </Routes> 复制代码
的时候 Route 被包裹了 Proxy Component,而 v6.x 的 Routes 组件只能允许 Route 作为自己的儿子组件,所以会报: Error: [ProxyFacade] is not a component. All component children of must be a or <React.Fragment>。