Hooks

先說說 Function Component

  • 在每一次 re-render 時, 都會重新 call 一次該 function 來創造 react elements
    • 代表著每一次利用 function 在創造 react elements 時, 擁有著 JavaScript closure 的特性
  • 對 Function Component 來說, 當作參數 input 的 props 以及 hook state 都只是常量, 它並不知道這些東西是會更動的

Hooks 的內部機制

  • 上一段說了 Function Component 的性質,那為何 hooks 的 useState(或其他 userXXX 系列) 每一次在 re-render 時可以記得上一次的值(包含了 state 以及 setState),而不會重新回傳一個新的

    1
    2
    3
    4
    function FC() {
    const [state, setState] = useState(...); // 這裡的 state 和 setState 神奇的存在某個地方
    ...
    }
  • 機制參考這: React hooks:它不是一种魔法,只是一个数组——使用图表揭秘提案规则

  • 也因此必須遵守: Ensure that Hooks are called in the same order each time a component renders
    • 避免將 useXXX 放在 condition block 裡面

useEffect

  • useEffect callback 只有在 指定的依賴(也就是第二個參數陣列裡的東西(通常會是 props or state)) 改變時, 才會執行
    • callback 內有用到的 props or state, 盡量確實寫在 dependency array 中, 避免不必要的錯誤
    • dependency array 為空, 代表著
      • callback 的本體只有在 FC 的第一次被 call時會執行
      • callback return 的 cleanup function 只有在 FC 要被 unmount 時才會執行
  • useEffect callback 是在 browers paint 完 UI 後才執行 (也就是 DOM tree 變更後)
    • 整個流程
      1. Component 把當下該顯示的 UI return 給 React
      2. React 渲染這次的 UI (Update VDOM)
      3. Browser repaint UI (Commit VDOM to DOM)
      4. React 處理 useEffect 定義的函數
        • (1) run 上一次 useEffect callback return 出來的 cleanup function
        • (2) run 這一次 useEffect callback function 的本體
  • useEffect callback 必須回傳 cleanup function or nothing, 因此 callback 不可以掛載 async (掛載 async 的 function 會回傳 Promise)

Fetch Data pattern

  • 包含了
    • isLoading
    • Cancel: 避免 data 回來 component 已經 unmount, set state 在已經 unmount 的 component 上, 會造成 memory leak
      • 一樣用到了 closure 特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function useData() {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);

useEffect(() => {
let didCancel = false;

(async () => {
try {
const remoteData = await someFetchAPI(...);
if (!didCancel) {
setData(remoteData);
}
} catch (err) {
console.log('Error occured:', err);
} finally {
if (!didCancel) {
setLoading(false);
}
}
})();

return () => {
didCancel = true;
}
}, []);

return { data, loading };
}

參考文獻