在觀念上我們會希望在 DOM Render 完之後才處理 Side Effect,在 Class Component 我們須在 componentDidMount()
與 componentDidUpdate()
lifecycle 分別處理;但在 Function Component 只需使用 Effect Hook 統一處理即可。
Version
macOS Mojave 10.14.6
WebStorm 2019.2.3
Node 12.11.0
Yarn 1.19.0
create-react-app 3.1.2
React 16.10.1
Class Component
import React, { Component } from 'react';
export default class extends Component {
state = {
count: 0,
};
componentDidMount = () => {
document.title = `You have clicked ${ this.state.count } times`;
};
componentDidUpdate = () => {
document.title = `You have clicked ${ this.state.count } times`;
};
addCount = () => {
this.setState({
count: this.state.count + 1
});
};
render() {
return (
<div>
<button onClick={ this.addCount }>+</button>
<div>{ this.state.count }</div>
</div>
);
}
}
當 DOM render 完後,希望能同時根據目前 count
state 更新 document.title
,此為 side effect。
第 8 行
componentDidMount = () => {
document.title = `You have clicked ${ this.state.count } times`;
}
當一開始 component 完成 mount 時,會執行 componentDidMound()
,可在此對 document.title
執行 side effect。
12 行
componentDidUpdate = () => {
document.title = `You have clicked ${ this.state.count } times`;
};
當每次 state 變動,DOM render 完時,會執行 componentDidUpdate()
,可在此對 document.title
執行 side effect。
我們可發現在
componentDidMount()
與componentDidUpdate()
出現重複 code,雖然可將重複部分 extract 成新 method,但仍無法避免在componentDidMound()
與componentDidUpdate()
都要呼叫新 method 的事實
Function Component
import React, { useState, useEffect } from 'react';
export default () => {
let [count, setCount] = useState(0);
useEffect(() => {
document.title = `You have clicked ${ count } times`
});
let addCount = () => setCount(count + 1);
return (
<div>
<button onClick={ addCount }>+</button>
<div>{ count }</div>
</div>
);
};
第 1行
import React, { useState, useEffect } from 'react';
將 useEffect()
import 進來。
第 6 行
useEffect(() => {
document.title = `You have clicked ${ count } times`
});
對 useEffect()
傳入 callback,當 DOM render 完後會呼叫該 callback。
useEffect()
的 callback 在 componentDidMount()
與 componentDidUpdate()
cycle 都會被呼叫,因此只要寫一次即可。
此外,藉由 ECMAScript 的 closure 特性,會自動抓到 count
state,因此不需要 this
存取 state。
因為每次 DOM render 完都會重新執行 useEffect()
,因此 callback 是重新傳入,此時剛好抓到 count
state 的最新值,因此每次 state 更新,useEffect()
的 callback 都會顯示最新的 state。
Conclusion
- Effect hook 其實背後黑魔法不少,但最少不必如 class component 要在
componentDidMount()
與componentDidUpdate()
各寫一次,只要觀念上是 DOM render 完執行 side effect 即可;且善用 closure 特性,不必使用this
存取 state,可放心使用 arrow function
Reference
Reed Barger, Introducing the useEffect Hook
React, Using the Effect Hook