12.2.2 热加载
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
12.2.2 热加载
在create-react-app产生应用的npm start指令下,每次对任何代码的修改,都会让浏览器中页面自动刷新,这样当然会让我们省去了手动刷新的麻烦,但是,有时候开发者并不想这样,比如,我们发现了一个Bug,一个最普通不过的Debug过程就是,修改一点代码,看看问题修复没有,没有就再修改一点代码,看看问题修复没有……直到问题消失了,不过,有的Bug很诡异,要在同一网页装载完成中做很多次操作之后才能复现,如果每改一点代码网页就刷新一次,开发者又要重复多次操作来看看是否修复了这个Bug,如果没有重来,又要手工重复多次操作……这样页面自动刷新就显得很烦。
试想,既然在React中遵守UI=render(state)这样的公式,在代码更新的时候,如果只更新render的逻辑,而不去碰state,那该多好!那样当发现render在某种state下暴露出bug,只需保持state不变,反复替换render就可以验证bug是否被修复。
对于上面Debug的过程,如果每次代码更改的时候,不要去刷新网页,而是让网页中的React组件渲染代码换成新的就行。因为在Redux框架下,我们把状态存在了Store上而不是在React组件中,所以这种替换完全可行。这种方式,叫做热加载(Hot Load)。目前create-react-app不支持热自动加载,但是我们现在自建服务器端,也就不受影响可以自己实现热自动加载了。
为了支持热自动加载,我们需要两个Express的中间件,一个叫webpack-dev-midd-leware,用于动态运行webpack生成打包文件,另一个叫webpack-hot-middleware,这个用于处理来自浏览器端的热加载请求,还需要一个babel装载起react-hot-loader,用于处理React组件的热自动加载。注意这两个中间件是Express的中间件,并不是Redux的中间件。
首先在命令行安装这三个npm库:
npm install --save-dev webpack-dev-middleware webpack-hot-middleware react-hot-loader
然后我们要修改config/webpack.config.dev.js,我们的“开发模式”服务器依然以这个文件作为webpack的配置。
默认的开发模式没有产生静态资源说明文件,但是我们的页面模板文件需要说明文件来获取JavaScript打包文件路径,所以,首先在文件顶部导入ManifestPlugin,然后在plugins部分添加ManifestPlugin实例,参数指定了产生asseet-manifest.json文件,这和npm build脚本产生的资源说明文件是一致的:
var ManifestPlugin = require('webpack-manifest-plugin');
plugins: [
...
new ManifestPlugin({
fileName: 'asset-manifest.json'
})
]
然后,在entry部分删掉或者注释掉原有的webpackHotDevClient,因为我们不想要网页自动刷新了,用另一个webpack-hot-middleware/client来取代它:
//require.resolve(‘react-dev-utils/webpackHotDevClient’),
‘webpack-hot-middleware/client’,
在loaders部分,增加react-hot的应用:
{
test: /.js$/,
include: paths.appSrc,
loader: 'react-hot'
},
为了让热加载有效,还需要保证webpack.HotModuleReplacementPlugin存在于plugins中,默认配置中已经有这个插件,所以无需修改。
最后,我们增加新的server/app.dev.js文件,和server/app.prod.js相比多了不少内容,先是要导入webpack-dev-middleware,代码如下:
const express = require('express');
const path = require('path');
const webpack = require('webpack');
const webpackConfig = require('../config/webpack.config.dev.js');
const compiler = webpack(webpackConfig);
const webpackDevMiddleware = require('webpack-dev-middleware')(
compiler,
{
noInfo: true,
publicPath: webpackConfig.output.publicPath
});
为了让express服务器支持开发者的工作,需要使用新安装的两个中间件,而这两个中间件都需要webpack作为支持,我们在JavaScript代码中创建webpack实例,指定配置文件就是config目录下的webpac.config.dev.js,和npm start使用的配置文件相同,习惯上,webpack实例的变量名被称为compiler。
在Express服务器启动的时候,webpack-dev-middleware根据webpack来编译生成打包文件,之后每次相关文件修改的时候,就会对应更新打包文件。因为更新过程只需要重新编译更新的文件,这个速度会比启动时的完全编译过程快很多,当项目文件量很大的时候尤其突出,这就是在开发模式中使用webpack-dev-middleware的意义。
而且,webpack-dev-middleware并没有将产生的打包文件存放在真实的文件系统中,而是存放在内存中的虚拟文件系统,所以要获取资源描述文件不能像产品环境那样直接require就行,而是要读取webpack-dev-middleware实例中的虚拟文件系统,对应的函数定义如下:
function getAssetManifest() {
const content = webpackDevMiddleware.fileSystem.readFileSync(__dirname + '/../build/asset-manifest.json');
return JSON.parse(content);
}
上面定义的getAssetMannifest文件读取的就是webpack-dev-middleware的虚拟文件系统中的打包说明文件。
虽然webpack-dev-middleware中间件能够完成实时更新打包文件,但是这只发生在服务器端,只有当浏览器刷新重新向服务器请求资源时才能得到更新的打包文件,而webpack-hot-middleware就更进一步,无需网页刷新,能够把代码更新“推送”到网页之中。
使用两个express中间件的代码如下:
const app = express();
app.use(express.static(path.resolve(__dirname, '../build')));
app.use(webpackDevMiddleware);
app.use(require('webpack-hot-middleware')(compiler, {
log: console.log,
path: '/__webpack_hmr',
heartbeat: 10 * 1000
}));
webpack-hot-middleware的工作原理是让网页建立一个websocket链接到服务器,服务器支持websocket的路径由path参数指定,在我们的例子中就是/__webpack_hmr。每次有代码文件发生改变,就会有消息推送到网页中,网页就会发出请求获取更新的内容。
最后,和“产品模式”的server/app.prod.js一样,需要用app.get指定一个默认的路由处理方式,对于所有非静态资源都返回一个ejs模板的渲染结果,代码如下:
app.get('*', (req, res) => {
const assetManifest = getAssetManifest();
return res.render('index', {
title: 'Sample React App',
PUBLIC_URL: '/',
assetManifest: assetManifest
});
});
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, 'views'));
module.exports = app;
在package.json中的scripts部分增加下面的一个指令:
"start:isomorphic ": "NODE_ENV=development node server/index.js",
然后,我们就可以在命令行用npm start:isomorphic命令启动开发模式的应用。
在浏览器中访问http://localhost:9000,可以看到程序界面,这时候我们尝试改变一个React组件的源代码,比如将src/pages/Home.js文件中的“Home”改成“主页”,可以发现,在浏览器中无需刷新网页,对应页面自动发生了变化。
在浏览器的Console中,可以看到整个自动更新的过程,如下所示:
HMR代表的就是Hot-Module-Reload,从日志中可以看到修改Home.js文件的更新被浏览器端发现并做了自动更新。
不过,也可以发现这样一行错误警告:
Warning: [react-router] You cannot change
这个错误警告是因为React-Router在热加载时的报警,这并不会产生什么实质危害,但是这个错误警告总是出现也很让人讨厌。我们可以修改src/Routes.js,把路由规则从Router的子组件中抽取出来作为独立变量,就可以解决这个问题,代码如下:
const routes = (
);
const Routes = () => (
{routes}
);
改完之后,这个错误提示就消失了。
现在我们已经有了服务器端渲染的开发模式和产品模式,接下来我们要实现真正的同构。 深入浅出React和Redux