deepthan/blog-angular

Angular热替换(cli & webpack)

deepthan opened this issue · 0 comments

webpack体系原生支持热替换功能,设置也非常简单,只须在webpack-dev-server命令下添加--hot参数:

webpack-dev-server --port=4200 --hot

加上这个hot参数会添加 HotModuleReplacementPlugin 插件到webapck配置里,并启动热替换功能。

如果你用的是 Angular-CLI 搭建,则添加 --hmr 即可开发服务器端的热替换功能:

ng serve --hmr

但是仅仅在开发服务器端开启热替换还不够,Angular 项目仍需要添加热替换处理逻辑,修改 Angular 的启动命令如下如下:

declare var module: any;
if(module.hot) { // 如果webpack启用了热替换功能
  // 接受模块更新的事件,同时阻止这个事件继续冒泡
  // 参考这文章:https://github.com/webpack/docs/wiki/hot-module-replacement-with-webpack
  module.hot.accept();
}
platformBrowserDynamic().bootstrapModule(AppModule)

这时候修改组件代码后,页面不再刷新了,而是把整颗组件树重新渲染,替换DOM节点。

但这样随之而来的一个问题是,在组件里保存的一些数据状态也会丢失,这样的体验显然不好。一个解决办法将组件的数据状态抽取出来外界管理,在热更新渲染前保存数据,渲染后还原数据,示例代码如下:

if(module.hot) {
	module.hot.accept();
	restoreState(module.hot.data.state); // 还原数据状态
	// 模块销毁前触发
	module.hot.dispose(() => {
		module.hot.data.state = getState(); // 保存数据状态
	});
}

webpack的热更新功能打开后,module.hot.data 是一个对象,可以用来在旧模块和新模块之间传递数据

这种有点类似于Redux的做法,Angular世界里其实也有这样工具库,如ngrx。
还有一个问题就是,重新渲染的过程中,一些老的样式不会被清理,导致header里的style标签堆积很多,所以在在重新渲染之前可先清理原来的style标签,示例代码如下:

if(module.hot) {
	module.hot.accept();
	module.hot.dispose(() => {
		let styles = document.head.querySelectorAll('style');
		for(let i = 0, len = styles.length; i++; i < len) {
			if(styles[i].innerText.indexOf('_ng') >= 0) {
				styles[i].remove();
				styles[i] = null;
			}
		}
	});
}

热替换功能不依赖ngrx。
热替换后的组件内数据会丢失,用ngrx来管理组件数据可以避免这个问题。