React 的那些事 — useState
身為一個 React 的開發者,在學習的路上踩過許多大大小小的坑,從 javascript 的基礎、side effect 的問題、rerender 的問題…,深入討論一些 React 的運作機制及可以改善的作法,會是這個系列的主軸。
這篇的主軸是要來探討 useState hook 的運作機制,主要動機是在小弟我還是菜鳥的時候去面試時,有被面試官問到這類的問題,但當時遇到的面試官看來觀念也不清楚,通常會讓你解釋一下底層的邏輯,接著問 useState 的運作是同步還是非同步?
要探討這類問題的話我們先來做個簡單的範例,如下:
以範例為例我們來模擬一下當這個component被渲染的時候的先後順序,當畫面剛進入時,會以你帶入的預設值 txt = “” & resData = [] 去渲染,所以會是空的input和空白的結果。
當我們於欄位中輸入 “a” 的時候會觸發 txtChange function 然後透過此function 的處理程序會先觸發了 setTxt 的 function 去更改 txt 的值為 “a”,然後我們又將 txt 的值帶入 fetch function裡面去打 api,並將回傳結果透過 setResData function 更改寫入 resData 中。
但這個時候你應該會發現 resData 的值並沒有更動,為什麼呢?讓我們重新看一遍這個 function 的處理順序:
const txtChange = (event) => {
setTxt(event.target.value);
// 這裡如果拿的是txt那麼會拿到原本就的值,其中的原理為js的閉包
// 並不是非同步的處理問題,也就是說你應該要改的是將txt改為event.target.value
// 因為透過setTxt的處理會經過一段diff的判斷處理程序
// 而你所取出來的setTxt又是在useState的function當中,所以這裡仍然為閉包的概念
console.log(txt) // 這裡你應該會拿到舊的值,也就是常常會讓人搞混的地方
fetch(`/apiUrlsForSearch?${txt}`) // 將txt改為event.target.value
.then((res) => res.json()).then(setResData)
}
這也是我真實經歷過的問題,面試官仍舊堅持自己的觀念為正確的,並確信useState為非同步的處理function,老實說他一舉這樣的例子我當下也無法解釋的那麼清楚,我只記得我肯定沒在官方文件上看到說明useState為非同步的解釋,只能怪自己太菜了。
也提醒各位如過遇到類似的情況請不要直接回嗆面試官,你可以在你心中扣分這分工作即可,因為如果他認知到自己的觀念不足也無法承認錯誤的話,在這樣的工作環境下你也不會成長,還有可能會以錯誤的觀念自以為正確的去誤導其他人,這樣會影響到你後續面試其他更好公司時的機會,與其跳入火坑不如換個好坑。
那麼其實更好一點的做法應該是將 fetch function 拆出來透過 useEffect 的方式去監聽原本的 txt 就好了:
const Search = () => {
const [txt, setTxt] = useState('');
const [resData, setResData] = useState([]);
useEffect(() => {
fetch(`/apiUrlsForSearch?${txt}`)
.then((res) => res.json()).then(setResData)
}, [txt])
const txtChange = (event) => {
setTxt(event.target.value);
}
return (
<div>
<input value={txt} onChange={txtChange} />
{resData?.map(...)}
</div>
)
}
這樣一來當你原本的txt更改的同時 useEffect 就會重複觸發 fetch function的部分,當然也可以透過 debounce 的做法來降低 server 的負擔。
這是剛好看到 Jack Herrington 的影片有感而發,想起那段跌跌撞撞的菜鳥時光,希望能幫到大家更了解一點 React 的運作原理。