跳至主要内容

React State Update

· 閱讀時間約 3 分鐘

此篇主要探討 React 的狀態(state)更新。 包括 React 如何知道 state 是否改變、不同資料型態的 state 更新方式及需要注意的細節。

什麼是 state ?

在 React 中,state 是 component 內部管理的狀態,會隨著 component 生命週期演進而改變。

為什麼需要更新 state ?

在 React 中,state 的更新會觸發 component 的 re-render,來達成畫面的更新。

React 如何知道 state 是否改變?

在討論如何以正確得方式更新不同資料型態的 state 前,我們要先知道,React 透過 Object.is 來判斷 state 是否改變,表示他是依據記憶體位置是否改變來判斷,記憶體位置若相同,則不會觸發 re-render。這麼做的原因是為了追求更好的效能,而我們只要開發者能確保永遠以 immutable 方式去更新 state,React 就可以幫我們免不必要的 re-render。

當我們說使用 immutable 方式更新 state 時,指的是,將所有資料視為 immutable 資料去更新。

不同資料型態的 state 更新方式

React 官方建議我們應該永遠都應該使用 immutable 方式去更新 state,因此在非 primitive type 的 state 更新上,我們要特別注意。 JS 中,變數又分為 primitive type 和 reference type,前者是 immutable,後者是 mutable。以下分別探討兩者的更新方式。

Primitive type 的更新

因為 primitive type 本身是 immutable,可直接用傳入新值的方式告知 React state 改變,即可正確觸發 re-render。

const [count, setCount] = useState(0)

setState(1)

Reference type 的更新

Reference type 的資料,如:Object, Array,是 mutable 的,代表即使值被改變,其記憶體位置可能不變,React 將無法辨識變化,造成不會 re-render。因此需確保傳入 setState 的新 state 是一份 deep copy

1. Object 的更新

  • 非 nested object

    • Object.assign
    const originalObj = { name: 'lix' }
    const clonedObj = Object.assign({}, originalObj)
    console.log(clonedObj === originalObj) //false
    • Spread operator ...
    const originalObj = { name: 'lix' }
    const clonedObj = { ...originalObj }
    console.log(clonedObj === originalObj) //false
  • nested object

    • 利用 recursive 自己實作一個 deep copy function

    • Json.stringify / Json.parse

      • 轉換時可能需要注意屬性出現非預期結果
    • Lodash 這樣的套件提供的現成的 cloneDeep 方法

      • 缺點:僅僅這個 function 就要多 17 kb 左右
    • immer 有 useImmer 讓我們可以使用 mutable 語法撰寫 immutable 程式碼

    • built-in 方法 structuredClone

      • 優點:多種瀏覽器、node.js、bun 都支援,包括 nested object and array 都可以安心使用
      • 缺點:仍有一些不支援的資料類型要注意,如: function, DOM node 等等

2. Array 的更新

使用會回傳新的 Array 的內建 function,如:map, filter, slice, concat。 避免使用會修改原 Array 的內建 function,如:push, pop, shift, unshift, splice。

  • 錯誤作法: 直接 mutate 原 Array,即使使用 setState,但記憶體位置不變,React 無法得知內容物是否變化,所以不會有 re-render。

    const [amount, setAmount] = useState([1, 2, 3])
    setAmount(amount.push(4))
  • 正確作法: 使用會回傳新的 Array 的內建 function,搭配 setState 註冊狀態更新,正確觸發 re-render。

    const [amount, setAmount] = useState([1, 2, 3])
    setAmount(amount.map(i => i * 2))
    setAmount(prev => prev.map(i => i * 2))

以 immutable 方式更新 state 的其他優點

避免副作用: 使用 mutable 方式更新可能導致 side effect,代表修改資料時可能會影響到其他部分的程式碼,導致錯誤或難以預測的行為。

Reference

React docs: Updating Objects in State 為什麼 React 中的 state 必須是 immutable?