1. 程式人生 > 實用技巧 >React函式式元件值之useEffect()

React函式式元件值之useEffect()

  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 }