有些需要 Subscribe 的 Side Effect,如 addEventListener()
,最後需要做 Unsubscribe 處理,否則會造成 Memory Leak,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 = {
x: null,
y: null
};
componentDidMount = () => {
window.addEventListener('mousemove', this.handleMouseMove);
};
componentWillUnmount = () => {
window.removeEventListener('mousemove', this.handleMouseMove);
};
handleMouseMove = event => {
this.setState({
x: event.pageX,
y: event.pageY
});
};
render() {
return (
<div>
<h2>Mouse Position</h2>
<p>X position: { this.state.x }</p>
<p>Y position: { this.state.y }</p>
</div>
);
}
}
24 行
render() {
return (
<div>
<h2>Mouse Position</h2>
<p>X position: { this.state.x }</p>
<p>Y position: { this.state.y }</p>
</div>
);
}
當滑鼠移動時,會自動顯示其 X 座標與 Y 座標。
第 4 行
state = {
x: null,
y: null
};
宣告 x
與 y
state,用來紀錄滑鼠移動時的 X 座標與 Y 座標。
第 9 行
componentDidMount = () => {
window.addEventListener('mousemove', this.onMouseMove);
};
當 component 完成 mount 時,對 browser subscribe mousemove
event 的 handler 為 onMouseMove()
。
17 行
onMouseMove = event => {
this.setState({
x: event.pageX,
y: event.pageY
});
};
當 mousemove
event 觸發時,會執行 onMouseMove()
將 X 軸座標與 Y 軸座標寫入 x
與 y
state。
13 行
componentWillUnmount = () => {
window.removeEventListener('mousemove', this.handleMouseMove);
};
當 component 完成 unmount 時,對 browser unsubscribe mousemove
event。
Class component 的 lifecycle 使得我們將明明是同一件事情的
addEventListener()
與removeEventListener()
要分開寫,除了很容易忘記外,也較不易維護
Function Component
import React, { useState, useEffect } from 'react';
export default () => {
let [mousePosition, setMousePosition] = useState({ x: null, y: null});
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
let handleMouseMove = event => {
setMousePosition({
x: event.pageX,
y: event.pageY
})
};
return (
<div>
<h2>Mouse Position</h2>
<p>X position: { mousePosition.x }</p>
<p>Y position: { mousePosition.y }</p>
</div>
);
};
第 1行
import React, { useState, useEffect } from 'react';
將 useEffect()
import 進來。
第 4 行
let [mousePosition, setMousePosition] = useState({ x: null, y: null});
宣告 mousePosition
state 為 object,包含 x
與 y
property。
第 6 行
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
useEffect()
的第一個 argument 除了傳入處理 side effect 的 function 外,若需清理 side effect,只需回傳另一個專門處理 side effect 的 function。
在 class component 我們是在 componentDidMount()
執行 addEventListener()
,componentWillUnmount()
執行 removeEventListener()
,也就是只有在 component 的 mount 與 unmount 才執行。
但若改用 function component,則每次 DOM render 都會執行 useEffect()
的 effect function,雖然結果看起來一樣,但實際上與原本 class component 的意義已經不同。
useEffect()
另外提供了第二個 arguement,每次 DOM render 完,若 []
內的 state 有變動,則會執行該 effect hook。
因為我們不希望每次 DOM render 就執行 effect hook,因此傳入 []
empty array,如此可避免每次 DOM render 完就執行,等效於 componentDidMount()
與 componentWillUnmount()
lifecycle 只執行一次,而不會在 componentDidUpdate()
lifecycle 執行。
Function component 的 effect hook 使得我們將同一件事情的
addEventListener()
與removeEventListener()
寫在同一個 function,除了不容易忘記外,也較易維護
Conclusion
- 一個 function component 可以有多的
useEffect()
,可將相關的 side effect 寫在同一個useEffect()
,而不必如 class component 必須遷就 lifecycle,導致同一件事情要分散在不同 lifecycle - 若 side effect 只需在
componentDidMount()
與componentWillUnmount()
lifecycle 執行,須在useEffect()
的第二個 argument 傳入[]
empty array,表示不依賴任何 state 變動
Reference
Reed Barger, Cleaning up Side Effects in useEffect
React, Using the Effect Hook
React, useEffect()