iT邦幫忙

2021 iThome 鐵人賽

DAY 1
1
Modern Web

用30天更加認識 React.js 這個好朋友系列 第 1

Day1-鐵人賽大綱 & 為什麼要使用 Hook?

  • 分享至 

  • xImage
  •  

鐵人賽大綱

在這次鐵人賽的挑戰中,主要的目標是希望自己和讀者們可以更加認識 React.js 或者是複習一下以前學過的知識,所以安排了幾個單元來進行介紹,預期介紹的單元如下:

  1. React hooks(會帶到 React Context)
  2. React 表單驗證
  3. Redux & Redux toolkit
  4. React Router v5
  5. React 動畫
  6. 其他瑣碎 React 特性功能

註: 本系列挑戰文會全部以 functional component 去做介紹

為什麼要使用 Hook?

那在進入 React hooks 單元前,先來了解一下什麼是 hook?為什麼要使用 Hook?

在 React.js 的官網提到

Hook 是 React 16.8 中增加的新功能。它讓你不必寫 class 就能使用 state 以及其他 React 的功能。

1. 因應 functional component 而生

回顧 React 的發展過程,從 class component 逐漸轉向使用 function component 為主,可以讓我們寫程式的邏輯可以不用再和生命週期函式綁定在一起,在設計上更有彈性,但純粹的 function component 本身要渲染到網頁畫面上時無法去控制和記憶狀態,所以這個部分就透過 hooks 去做補足,換句話說就是讓 functional component 能夠擁有狀態,然後 functional component 和 React hooks 都以函式的形式撰寫,這樣的好處是開發者可以自由的定義函式的傳入參數和回傳值,在設計程式碼上更有彈性。

2. 提升狀態邏輯的重用性

在過去,使用 class component 時需要使用 HOC(Higher Order Component) 與 Render Props 去進行 Code Reuse,但多層的 HOC 會造成 wrapper hell,維護性不佳,並且多層的 HOC 也可能會有 Props 命名衝突的問題。

class component 很多的程式碼也都和生命週期函式做綁定,如果有重複性邏輯會不好抽取出來。

但我們現在可以將和某個小功能相關的程式碼邏輯放在同一個 custom hook 裡面,讓不同的元件需要該小功能時,就可以使用該 hook,減少程式碼的重複性,也更方便維護該功能。

每次呼叫一個 Hook 時,都會有一個完全獨立且隔離的 state,所以你甚至可以在一個 component 使用同一個客製化 Hook 兩次

使用 hook 的限制

看起來 hook 確實很強大,但它也有些使用上的限制:

  1. 只在元件最上層使用 Hook,不能在 loop、條件判斷、nest function(在 function scope 中宣告的 function)調用hook。
  2. 名稱必須是以 use 開頭,包括自己建立的 custom hook 也是
  3. 只在 React function component 呼叫 Hook

為什麼 React hook 要盡量在 function component 裡的頂層調用,不可以在條件式或迴圈、巢狀函式裡等地方調用?

這邊可以和 Day29 Fiber 段落的內容一起觀看,我們如果在元件內部使用了多個 hooks,它們的相關狀態、資料等會形成一個 linked list,每次渲染時就會依序照著這個 linked list 去執行 hook。

https://ithelp.ithome.com.tw/upload/images/20230610/20116883HveffBsFkm.png

這樣設計的好處是 hooks 在多個元件複用時,可以不用擔心會有重複命名的問題,而是透過像是索引的方式去區別各個的 hooks,因此如果在條件式、迴圈內部或是巢狀函式等地方使用到 hooks,每次渲染就有可能不會執行到某些 hooks 進而導致順序錯亂。

React hooks: not magic, just arrays 這篇文章的 And the naive implementation 段落,有模擬一個簡化版本的 useState,作者提到這不是用來表達 hook 底層的運作方式,但簡化版本的 useState 可以更容易讓你了解為什麼 React hook 只能在 function component 裡的頂層調用的原因。

在範例程式碼中,使用了 state 和 setters 兩個陣列還有 cursor 計數的變數,而每使用一個 useState hook,就會將 state 和 setters 兩個陣列推入新的元素,然後 cursor 加一。所以 state 會分別存 firstName 和 lastName 值,setters 存 setFirstName 和 setLastName,此時每次渲染元件時,useState 的回傳值就是透過 cursor 當作索引去分別取出 state 和 setters 兩個陣列的元素,索引值不對就會更新到不正確的 state,這樣就很容易理解為什麼 hooks 會有這個限制的原因了。

const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1

在我們了解 hook 是什麼及為什麼要使用它後,接著就來認識一下有哪些 hook 吧!


2025/03/10 使用 ChatGPT 補充-為什麼 React hook 在原始碼的部分,要設計成鏈表 linked list?

React Hooks 之所以在原始碼中設計成 鏈表(linked list),主要是為了提高效能並確保在函數組件的渲染過程中能夠正確維護 hooks 的狀態。以下是幾個關鍵原因:

1. 確保 Hooks 調用順序的一致性

在 React 中,Hooks 必須按照相同的順序調用,因為 React 內部是根據 Hook 調用的順序來匹配狀態的。
若使用陣列來儲存 hooks,當有新的 Hook 插入或刪除時,索引會發生變化,導致對應的狀態錯誤。

鏈表的優勢

  • 鏈表的節點間彼此參考,允許插入或刪除節點而不影響其他節點。
  • 確保 Hook 調用的順序不會因為插入或刪除某個 Hook 而發生錯位。

2. 高效的記憶體管理

如果使用 陣列(Array) 來儲存 hooks,每次組件重新渲染時都可能需要擴展或重新分配記憶體,這會帶來額外的開銷。
鏈表(Linked List) 只需要更新節點的 next 指針即可,記憶體分配更靈活。

3. 避免不必要的重新計算

React 在 同一個組件的多次渲染過程中,會重新執行函數組件(FC),但 hooks 的狀態應該保持不變。
鏈表的結構允許 React 在 重新渲染時復用先前的 hooks,而不需要重新計算。

具體方式

  • React 會維護一個 currentHook 指標,指向當前處理的 Hook 節點。
  • 每次 useStateuseEffect 等 Hook 調用時,React 會沿著鏈表移動 currentHook 指標,而不是透過索引尋找對應的狀態。

4. 支援 Fiber 架構與並發模式

React 內部使用 Fiber 架構,這是一種 類似鏈表的結構,允許 React 進行可中斷的渲染。
Hooks 使用 鏈表結構 可以與 Fiber 整合,方便在並發模式(Concurrent Mode)下執行。


下一篇
Day2-React Hook 篇-認識 useState
系列文
用30天更加認識 React.js 這個好朋友33
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言