useEffect()
的第二個 Argument 可傳入 Array,指定當特定 State 改變時,才執行 Effect Function。
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
Empty State Dependency
import React, { useState, useEffect } from 'react';
export default () => {
let [count, setCount] = useState(0);
let [mousePosition, setMousePosition] = useState({ x: null, y: null});
useEffect(() => {
document.title = `You have clicked ${ count } times`;
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
let addCount = () => setCount(count + 1);
let handleMouseMove = event => {
setMousePosition({
x: event.pageX,
y: event.pageY
})
};
return (
<div>
<button onClick={ addCount }>+</button>
<div>{ count }</div>
<h2>Mouse Position</h2>
<p>X position: { mousePosition.x }</p>
<p>Y position: { mousePosition.y }</p>
</div>
);
};
第 7 行
useEffect(() => {
document.title = `You have clicked ${ count } times`;
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
Effect function 內含有兩個 side effect,一個是改變 document.title
,另一個使用 window.addEventListener()
註冊 mousemove
event。
為了讓註冊 mousemove
event 只在 componentDidmount()
與 componentWillUnmount()
lifecycle 執行,而不會在 componentDidUpdate()
lifecycle 執行,所以特別在 useEffect()
的第二個 argument 傳入 []
empty array,表示任何 state 變動都不會執行 effection function。
但實際執行會發現這導致了 document.title
也不再因為 count
state 改變而改變了。
State Dependency
import React, { useState, useEffect } from 'react';
export default () => {
let [count, setCount] = useState(0);
let [mousePosition, setMousePosition] = useState({ x: null, y: null});
useEffect(() => {
document.title = `You have clicked ${ count } times`;
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [count]);
let addCount = () => setCount(count + 1);
let handleMouseMove = event => {
setMousePosition({
x: event.pageX,
y: event.pageY
})
};
return (
<div>
<button onClick={ addCount }>+</button>
<div>{ count }</div>
<h2>Mouse Position</h2>
<p>X position: { mousePosition.x }</p>
<p>Y position: { mousePosition.y }</p>
</div>
);
};
第 7 行
useEffect(() => {
document.title = `You have clicked ${ count } times`;
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [count]);
解決方式是在 useEffect()
的第二個 argument 以 [count]
傳入,表示 effect function 除了在 componentDidMount()
與 componentWillUnMount()
lifecyle 執行外,在 componentDidUpate()
時當count
state 改變時,就會執行 effect function。
Separate useState()
import React, { useState, useEffect } from 'react';
export default () => {
let [count, setCount] = useState(0);
let [mousePosition, setMousePosition] = useState({ x: null, y: null});
useEffect(() => {
document.title = `You have clicked ${ count } times`;
});
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
let addCount = () => setCount(count + 1);
let handleMouseMove = event => {
setMousePosition({
x: event.pageX,
y: event.pageY
})
};
return (
<div>
<button onClick={ addCount }>+</button>
<div>{ count }</div>
<h2>Mouse Position</h2>
<p>X position: { mousePosition.x }</p>
<p>Y position: { mousePosition.y }</p>
</div>
);
};
第 7 行
useEffect(() => {
document.title = `You have clicked ${ count } times`;
});
將改變 document.title
的 side effect 獨立出來,如此每次 count
改變 document.title
都會改變。
11 行
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
將註冊 mousemove
event 的 side effect 獨立出來,如此只有 componentDidMount()
與 componentWillUnmount()
lifecycle 才會執行。
Conclusion
- 雖然可將多個 side effect 寫在一起,透過
useEffect()
的第二個 argument 指定要相依的 state 改變,但實務上還是建議分多個useEffect()
寫,維持一個useEffect()
一個 side effect,這更符合原本 effect hook 設計本意,也更為清楚
Reference
Reed Barger, Cleaning up Side Effects in useEffect
React, Using the Effect Hook
React, useEffect()