react项目热更新升级
前言
为啥总结
不好使: router路由 + HMR热替换 + code splitting代码分割,这3者两两结合倒还好,一旦放一起就完犊子了,特别体现在:无法HMR热替换不是太好使,以前开发时的解决方案有4个:
1.直接刷新页面;2.保存两次才能触发HMR热替换;3.开发时单独对子组件关闭代码分割;4.单独在子组件外调用react-hot-loader.hot方法
太旧了: code splitting代码分割插件React-Loadable 已不再维护,导致开发中会报警告,看着糟心
没参考:讲真,百度、谷?歌了许久,都没有一篇文章完整的详述【不好使】里的三者如何有效结合,所以决定对比研究后好好把这篇文章肝出来总结一下 。
咋办
- 在不影响当前业务的情况下,向后兼容,尽量少的改动 + 尽量少的连带问题出现,具体方案建文章末尾【最终方案】
啥时候
- 2020/08
react-hot-loader (老版HMR)简介
用法
详情:https://github.com/gaearon/react-hot-loader npm install react-hot-loader | yarn add react-hot-loader
步骤1.添加 react-hot-loader/babel 到 babelrc或babel.config.js
// .babelrc
{
"plugins": ["react-hot-loader/babel"]
}
步骤2.根组件(一般为:App.js)标记hot输出口
// App.js
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);
步骤3.在react/react-dom之前引入react-hot-loader
方法1: 主函数(一般为src/index.js)全局引入react-hot-loader
/*
* import { AppContainer } from 'react-hot-loader';
* 此为老版API的用法,不再推荐,调用时:
* <AppContainer>
* <Component />
* </AppContainer>,
*/
/*
* import { hot } from 'react-hot-loader';
* 此为老版API的用法,不再推荐,调用时:
* hot(module)(App);
*/
import 'react-hot-loader'; // 在react/react-dom之前引入
import React from 'react';
import { render } from 'react-dom';
import App from './app';
let root = document.getElementById('root');
render(<App />, root);
方法2:webpack配置文件添加入口
// webpack.config.js
module.exports = {
entry: ['react-hot-loader/patch', './src'],
// ...
};
[可选]步骤4.如果需要支持hooks,请使用@hot-loader/react-dom
代码分割Code Splitting搭配时
尽量使用插件:
react官方自带
不自带优雅降级, 不自带异常捕获边界
2019年以前最火的懒加载库,2018年8月开始不再发布新版本 自带优雅降级, 自带异常捕获边界,会报一些要求升级的警告
个人开发者,卖点是: 动态import(./${value}) ,可以拼接传参
自带优雅降级, 不自带异常捕获边界
个人开发者,卖点是: 支持babel-macro (宏),同时帮忙维护@loadable/component 自带优雅降级, 半自带异常捕获边界(断网后重载不太好使)
2018年7月开始不再发布新版本 都不更新了,就甭看了
对比一下
Library【库名】 Suspense 【自带优雅降级】 Error Boundaries 【异常捕获边界】 SSR 【异常捕获边界】 Hooks import (./${value}) babel-macro 【宏】 React.lazy ❌ ❌ ❌ ❌ ❌ ❌ react-loadable ✅ ✅ ✅ ❌ ❌ ❌ @loadable/component ✅ ✅ ✅ ❌ ✅ ❌ imported-component ✅ ❌ ✅ ✅ ❌ ✅
弃用 react-hot-loader 原因
1. 官方支持
官方的支持,除了光环之外,还带来性能与稳定性保障,对 hook 更完善的支持...
How should we set up apps for HMR now that Fast Refresh replaces react-hot-loader?(官方给出的简要使用教程)
2. 更低的侵入性
不必在项目代码中用 hot(App) 的形式来标记组件,或是在 webpack.entry 中注入额外代码
3. react-hot-loader 官方退役声明
引用官方声明
React-Hot-Loader is expected to be replaced by React Fast Refresh. Please remove React-Hot-Loader if Fast Refresh is currently supported on your environment.
翻译:预计 React-Hot-Loader 将被 React快速刷新 取代。如果您的环境支持 Fast Refresh,那么请删除 React-Hot-Loader。
启用 react-refresh-webpack-plugin (新版HMR)(react-dom@16.9+)
用法
详情:https://github.com/pmmmwh/react-refresh-webpack-plugin/ npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh 或者 yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
步骤1.webpack.config.js添加配置(只服务于开发环境)
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
plugins: [
// 其他插件
// 传入【options】overlay参数,直接屏蔽掉报错遮罩层,此遮罩层会置于最上层,影响调试
isDevelopment && new ReactRefreshWebpackPlugin({ overlay: false }),
].filter(Boolean),
};
步骤2.更新Babel配置(只服务于开发环境)
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
plugins: [
// 其他插件
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
};
方法1: webpack.config.js添加配置
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
// ... other loaders
{
loader: require.resolve('babel-loader'),
options: {
plugins: [isDevelopment && require.resolve('react-refresh/babel')].filter(Boolean),
},
},
],
},
],
},
};
方法2: babelrc或babel.config.js中添加配置
module.exports = (api) => {
api.cache.using(() => process.env.NODE_ENV);
return {
plugins: [
!api.env('production') && 'react-refresh/babel',
].filter(Boolean),
};
};
注意事项
- react-refresh 最低支持版本 react-dom@16.9+,如果添加到老项目中后,热更新没有生效,那么首先请确认版本是否满足要求。
- 入口文件不支持 HMR,如果在入口文件中直接修改组件,会降级为 LiveReload。
- webpack 的 externals 配置项会导致 react-refresh 失效,在 dev环境下可以先关闭配置。
最终方案
router路由:react-router
- 不再赘述
HMR热替换:react-refresh-webpack-plugin
- 不再赘述
code splitting代码分割:@loadable/component
import LazyCom from 'components/LazyComp'; <Route basename={basename} exact path="/some-component" component={LazyCom(() => import('pages/SomeComponent'))}></Route>
Suspense优雅降级:@loadable/component引入loading加载层
// components/LazyComp.js import loadable from '@loadable/component'; import Loading from './Loading'; export default loader => loadable(loader, { fallback: <Loading/> });
Error Boundaries异常捕获边界 : react-error-boundary,第三方插件,覆盖的情况比较广泛
import { ErrorBoundary } from 'react-error-boundary'; const ErrorFallback = ({ error, resetErrorBoundary }) => { console.error(error.message); return <Button onClick={resetErrorBoundary}>重试</Button>; }; <Layout><Content><ErrorBoundary FallbackComponent={ErrorFallback}> <StaticRoutes routes={routes}/> </ErrorBoundary></Content></Layout>