Working with React State & useState Hook
2021-09-03
本文介紹 React 中 State 的相關概念,包含 useState Hook、Snapshot、Lazy State Initialization 等觀念。
初始化與重新渲染
- 初始化 (Initialization):State 只會在第一次載入時初始化,除非 DOM 移除,否則
useState
管理的 State 都不會重新初始化 - 重新渲染 (Re-rendering):某狀態改變會導致元件重新繪製,不過只會更改該一狀態,其他狀態不會進行初始化(但是會執行初始化行為,算是小坑,本文最後會提到)
如果是第一次 Render,State 的值會是 initialState,但如果是後續的 Re-render,initialState 將不會被使用,State 會維持是最近一次更新的 State 值。
這是 React 保證的,也是為什麼後續 useEffect
不必把 State 放入 Dependencies。
更改狀態
進行 setState
時並不會馬上更改狀態,而是會進入排程,優先完成更重要的工作,不過 React 還是會確保我們改變 State 的順序是正確的。
例如:以下排程多個 setState
,React 會到最後才按照排程順序執行狀態改變,結果 State 就會得到 Book。
const [state, setState] = useState('DVD');
setState('Pencil');
setState('Book'); // 最後的狀態會變成 Book
取得狀態快照
有時候我們需要馬上取得改變的結果,方便去做一些應用或計算。這時候我們可以透過「函式」的形式,讓狀態更新可以依賴於先前的狀態快照 (Snapshot)。
這樣我們就能得到基於狀態快照 (Snapshot) 的新狀態,而不是永遠都只會拿到最新的那一個狀態哩!
// 👇 Updating State That Depends On The Previous State.
// Safer way: Ensure the Latest state snapshot (If it depends on the previous state)
setUserInput((prevState) => {
return { ...prevState, enteredTitle: event.target.value };
});
// Complex state: arrays
setUserInput((prevArray) => {
return [...prevArray, { enteredTitle: event.target.value }];
});
// Complex state: objects
setUserInput((prevObject) => {
return { ...prevObject, enteredTitle: event.target.value };
});
如果新狀態的改變是依賴於前一個狀態的話,就要用這種 Functional Return 的形式,以取得最新的狀態。
另外,除了上面這個方法,我們也可以借助 useEffect
來完成這個效果。useEffect
可以監聽 Dependencies 裡面的 State 有沒有改變,如果改變就執行 setState
,因此也不會錯過狀態改變,能夠確保取得最新的狀態。
也因此,在 useEffect
裡使用的 setState
的時候,即使不是寫成函式的形式,也會即時更新狀態喔!
Lazy State Initialization
當一個元件裡的狀態被改變時,就會重新渲染那個元件,而在做 re-render 時 useState
的 initial value 雖然是沒用的,但是其實每次 re-render 時還是會再跑一次 initial value。
聽起來影響不大是嗎?畢竟沒影響到畫面呈現?
如果是在代價很低的情況下可能沒差,像是每次 re-render 都重新跑一次 console.log
或許無所謂。
const [state, setState] = React.useState(console.log('State initialization'));
但是如果初始 State 是一些需要複雜邏輯計算、效能耗費較高的狀態時,像是從 Local Storage 讀取值,那就必須考量到效能問題了。
const [state, setState] = React.useState(
JSON.parse(localStorage.getItem('notes')) || []
);
這時候我們可以運用 useState
的一個特性,就稱之為懶惰的初始狀態好了,當我們想要避免執行 Initialize 時,就可以用這個 Lazy Initialization 的方法初始化 State。
寫法就是在 useState
原本傳入 value 的地方,改為傳入 Function 來 return 初始值。當傳入的是一個 Function 時,這個 Initial Function 就只會在初始 Render 時被調用執行,後續 Re-rendering 時不會再執行。
Lazily initialize our
notes
state so it doesn't reach into localStorage on every single re-render of the App component.
const [notes, setNotes] = React.useState(
() => JSON.parse(localStorage.getItem('notes')) || []
);
Recap
- State
- useState Hook
- Snapshot
- Lazy State Initialization