前言

今天我们来聊一聊react的模块热更新,熟悉react的同学都知道,使用官方的脚手架,当我们改动页面时,整个页面会被刷新,那我们当然希望页面只刷新我们改动的部分而不是刷新整个页面,那么怎么做到如此呢?千呼万唤始出来,react-hot-loader就是来完成这个伟大的使命的。

1. 安装react-hot-loader

1
npm i react-hot-loader --save

2. 在开发环境下添加入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// webpack.config.js
entry: [
isEnvDevelopment && 'react-hot-loader/patch',
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),

如上,entry的第一行即为我们添加的内容。

3. 在开发环境下配置babel插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),

plugins: [
isEnvDevelopment && 'react-hot-loader/babel',
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},

如上,plugins配置的第一行:isEnvDevelopment && 'react-hot-loader/babel',即为我们添加的代码,如果配置在.babelrc文件中,则写在.babelrc的plugins配置中。

4. plugins中添加依赖的 HotModuleReplacement 插件

一般情况下,脚手架初始化的项目此步骤已经满足,无需再改动。

1
2
3
4
5
plugins: [
...
new webpack.HotModuleReplacementPlugin(), //设置这里
...
]

5. 设置 devServer 的 hot 为 true

一般情况下,脚手架初始化的项目此步骤已经满足,无需再改动。

1
2
3
4
5
devServer: {
...
hot: true, //设置这里
...
},

6. 在项目主入口文件中进行修改

首先,从 react-hot-loader 导入 AppContainer 组件:

1
import { AppContainer } from 'react-hot-loader';

然后,改写 render 方法:

原 render 为:

1
2
3
4
5
6
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

修改之后为:

1
2
3
4
5
6
7
8
9
const render = Component => {
ReactDOM.render(
<Provider store={store}>
<AppContainer>
<Component />
</AppContainer>
</Provider>,
document.getElementById('root'));
}

然后,执行render函数,继而再写模块热更新时的处理代码:

1
2
3
4
5
6
7
8
9
10
render(App);

if (module.hot) {
module.hot.accept('./App', () => {
//因为在App里使用的是export default语法,这里使用的是require,默认不会加载default的,所以需要手动加上
const NextApp = require('./App').default;
// 重新渲染到 document 里面
render(NextApp);
})
}

完整的主入口文件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from '@/store';
import * as serviceWorker from './serviceWorker';

import { AppContainer } from 'react-hot-loader';

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


render(App);

if (module.hot) {
module.hot.accept('./App', () => {
//因为在App里使用的是export default语法,这里使用的是require,默认不会加载default的,所以需要手动加上
const NextApp = require('./App').default;
// 重新渲染到 document 里面
render(NextApp);
})
}

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

至此,我们就已经配置完成了react项目的模块热更新,即局部刷新而不是整个页面的刷新,如此可大大提高开发效率。