## 模块热替换(Hot Module Replacement)—— 有点问题
到目前为止,当我们修改代码的时候,浏览器会自动刷新,不信你可以去试试。
不过我们可以学习一下把别的项目改成支持热更新模块。
可以参考一下[webpack模块热替换](https://doc.webpack-china.org/guides/hot-module-replacement)教程。
首先在`package.json`增加`--hot`。
~~~
"dev": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
~~~
`src/index.js` 增加`module.hot.accept()`,如下。当模块更新的时候,通知`index.js`。
~~~
import React from 'react'
import ReactDom from 'react-dom'
import Router from './router/router'
if (module.hot) {
// 命令行 --hot
module.hot.accept()
}
ReactDom.render(<Router />, document.getElementById('app'))
~~~
现在我需要说明下我们命令行使用的`--hot`,可以通过配置`webpack.dev.config.js`来替换,
向文档上那样,修改下面三处。但我们还是用`--hot`吧。下面的方式我们知道一下就行,我们不用。同样的效果。
~~~
const webpack = require('webpack')
devServer: {
hot: true
}
plugins:[
new webpack.HotModuleReplacementPlugin()
]
~~~
HRM配置其实有两种方式,一种命令行`CLI`方式,一种`Node.js API`方式。我们用到的就是命令行`CLI`方式,比较简单。
`Node.js API`方式,就是建一个`server.js`等等,网上大部分教程都是这种方式,这里不做讲解了。
但是上面的配置对react模块的支持并不是太好。
比如写个demo,当模块热替换的时候,state会重置,而我们则希望能保持原有的数据状态。
在原来的`Home.js`,增加一个数据状态`state`
`src/pages/Home/Home.js`
~~~
import React, { Component } from 'react'
export default class Home extends Component {
// 新增
constructor(props) {
super(props)
// 一般在构造函数内初始化state
this.state = {
count: 0
}
}
// 新增(私有方法用_开头)
_handleClick() {
this.setState({
count: ++this.state.count
})
}
render() {
return (
<h1>
This is Home
<p> 当前计数:{this.state.count} <button onClick={() => this._handleClick()} >自增+1</button> </p>
</h1>
)
}
}
~~~
这时候可以看到页面初始化count为0,点击自增+1会改变count,然后当修改代码的时候,webpack更新了页面的同时,也把count初始化为0了。
为了在react模块更新的同时,能保留state等页面中其他状态,我们需要引入[react-hot-loader](https://github.com/gaearon/react-hot-loader)。
Q: 请问`webpack-dev-server`与`react-hot-loader`两者的热替换有什么区别?
A: 区别在于`webpack-dev-server`自己的`--hot`模式只能即时刷新页面,但状态保存不住。因为`React`有一些自己语法(JSX)是`HotModuleReplacementPlugin`搞不定的。
而`react-hot-loader`在`--hot`基础上做了额外的处理,来保证状态可以存下来。(参考[segmentfault](https://segmentfault.com/q/1010000005612845))
下面我们来加入`react-hot-loader v3`,
> 记录于2017-12-20 现在发现react-hot-loader v3.1.3以下报错无法使用,因为不支持按需加载那边的HOC。
> 有两种解决办法:
> 1. 等v3.1.4发布后,就会解决这个问题。
> 2. 不要使用按需加载。
所以我们来安装下一个版本的依赖:
`npm install react-hot-loader@next --save-dev`
根据[文档](https://gaearon.github.io/react-hot-loader/getstarted/),我们做如下几个修改:
1. `.babelrc`增加`react-hot-loader/babel`
~~~
/* .babelrc */
{
"presets": [
"es2015",
"react",
"stage-0"
],
// 新增
"plugins": [
"react-hot-loader/babel"
]
}
~~~
2. `webpack.dev.config.js` 入口增加`react-hot-loader/patch`
` webpack.dev.config.js`
~~~
/*入口*/
// entry: path.join(__dirname, 'src/index.js')
/*从单入口变成多入口*/
entry: [
'react-hot-loader/patch',
path.join(__dirname, 'src/index.js')
]
~~~
3. `src/index.js`修改如下
`src/index.js`
~~~
import React from 'react'
import ReactDOM from 'react-dom'
// 如果你不需要保存数据状态就不需要用react-hot-loader
import { AppContainer } from 'react-hot-loader'
import Router from './router/router'
/*初始化*/
renderWithHotReload( < Router / > )
/*热更新*/
if (module.hot) {
module.hot.accept('./router/router', () => {
// const getRouter = require('./router/router').default
renderWithHotReload( < Router / > )
// })
}
function renderWithHotReload(RootElement) {
ReactDOM.render(
<Router />,
document.getElementById('app')
)
}
~~~
参考[gaearon/react-hot-loader#243](https://github.com/gaearon/react-hot-loader/issues/243)