Effect Hook 可以讓你在函數組件中執行副作用操作,這里提到副作用,什么是副作用呢,就是除了狀態相關的邏輯,比如網絡請求,監聽事件,查找 dom。
可以這樣說,在使用了useState或是useEffect這樣的hooks之后,每次組件在render的時候都生成了一份本次render的state、function、effects,這些與之前或是之后的render里面的內容都是沒有關系的。而對於class component來說,state是一種引用的形式。這就造成了二者在一些表現上的不同。
一、基礎用法
定義一個函數和一個數組。函數體為組件初始化或變化時執行的代碼,返回值為組件銷毀前執行的代碼。數組參數中放的是觸發此函數的依賴項數據。
1 useEffect(() => { 2 // 相當於 componentDidMount、componentDidUpdate
3 console.log("code"); 4 return () => { 5 // 相當於 componentWillUnmount
6 console.log("code"); 7 } 8 }, [/*依賴項*/])
二、監聽參數
類組件在綁定事件、解綁事件、設定定時器、查找 dom 的時候,是通過 componentDidMount、componentDidUpdate、componentWillUnmount 生命周期來實現的,而 useEffect 會在組件每次 render 之后調用,就相當於這三個生命周期函數,只不過可以通過傳參來決定是否調用。
其中注意的是,useEffect 會返回一個回調函數,作用於清除上一次副作用遺留下來的狀態,如果該 useEffect 只調用一次,該回調函數相當於 componentWillUnmount 生命周期。
決定useEffect中各部分代碼角色的是第二個參數:
- 什么都不傳,組件每次 render 之后 useEffect 都會調用,相當於 componentDidMount 和 componentDidUpdate
- 傳入一個空數組 [], 只會調用一次,相當於 componentDidMount 和 componentWillUnmount
- 傳入一個數組,其中包括變量,只有這些變量變動時,useEffect 才會執行
1 function App () { 2 const [ count, setCount ] = useState(0) 3 const [ width, setWidth ] = useState(document.body.clientWidth) 4 const onChange = () => { 5 setWidth(document.body.clientWidth) 6 } 7 //最簡單用法
8 useEffect(() => { 9 //只有方法體,相當於componentDidMount和componentDidUpdate中的代碼
10 document.title = count; 11 }) 12 //加返回值用法
13 useEffect(() => { 14 //添加監聽事件,相當於componentDidMount和componentDidUpdate中的代碼
15 window.addEventListener('resize', onChange, false); 16 //返回的函數用於解綁事件,相當於componentWillUnmount中的代碼
17 return () => { 18 window.removeEventListener('resize', onChange, false) 19 } 20 }) 21 //加空數組參數用法
22 useEffect(() => { 23 // 相當於 componentDidMount
24 window.addEventListener('resize', onChange, false) 25 return () => { 26 // 相當於 componentWillUnmount
27 window.removeEventListener('resize', onChange, false) 28 } 29 }, []); 30 //加監聽值用法
31 useEffect(() => { 32 //只有當count的值發生變化,此函數才會執行
33 console.log(`count change: count is ${count}`) 34 }, [ count ]); 35 return ( 36 <div>
37 頁面名稱: { count } 38 頁面寬度: { width } 39 <button onClick={() => { setCount(count + 1)}}>點我</button>
40 </div>
41 ) 42 }
其實Function Component 不存在生命周期,把 Class Component 的生命周期概念搬過來試圖對號入座只是一種輔助記憶手段,Function Component 僅描述 UI 狀態,React 會將其同步到 DOM,僅此而已。
三、使用優化
我們在使用useState的時候,經常碰到capture value的問題,比如下面代碼會輸出5而不是3:
1 const App = () => { 2 const [temp, setTemp] = React.useState(5); 3 const printTime = () => { 4 setTimeout(() => console.log(temp), 3000); 5 }; 6 return ( 7 <div onClick={() => { 8 printTime(); 9 setTemp(3); 10 }} 11 >clickMe</div>
12 ); 13 };
在printTime函數執行的那個 Render 過程里,temp 的值可以看作常量 5,執行 setTemp(3) 時會交由一個全新的 Render 渲染,所以不會執行printTime函數。而 3 秒后執行的內容是由 temp 為 5 的那個 Render 發出的,所以結果自然為 5。
原因就是 temp、printTime 都擁有 Capture Value 特性。而useEffect 也一樣具有 Capture Value 的特性。
利用 useRef 就可以繞過 Capture Value 的特性。可以認為 ref 在所有 Render 過程中保持着唯一引用,因此所有對 ref 的賦值或取值,拿到的都只有一個最終狀態,而不會在每個 Render 間存在隔離。也可以簡潔的認為,ref 是 Mutable 的,而 state 是 Immutable 的。
1 function Example() { 2 const [count, setCount] = useState(0); 3 const latestCount = useRef(count); 4 useEffect(() => { 5 //設置最新變量的引用
6 latestCount.current = count; 7 setTimeout(() => { 8 //讀取引用指向的最新值
9 console.log(`You clicked ${latestCount.current} times`); 10 }, 3000); 11 }); 12 return ( 13 <div>
14 <p>You clicked {count} times</p>
15 <button onClick={() => setCount(count + 1)}>Click me</button>
16 </div>
17 ); 18 }