## 模塊熱替換(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)