Wen Chen
Published on

「React筆記」PrevState in useState

Authors
  • avatar
    Name
    Wen Chen

這算是搬到個人部落格後的第一篇文,但其實內容過去在 medium 就寫過了,不過最近剛好又在 debug 時遇到差不多的問題,於是重寫了一次算是複習一下。

useState是ReactJs中非常使用到也幾乎是不可或缺的hook之一,然而useState的「非同步」性,卻是很常受到忽略的一部分,底下會分享近期開發上遇到的問題與筆記。


還記得過去初探 ReactJS 時,曾寫下這樣子的 Code :

const [show, setShow] = React.useState(false)
const handleClick = ()=>{
    show ? setShow(false): setShow(true)
}
return (<div>
        <button onClick={handleClick}>button</button>
    </div>
);

想當然爾,上面那段 Code 肯定是壞了(否則也不會有這篇文)

上面那段程式碼,主要是要用state來控制某些元件的顯示與否,然而在實際測試之後,發現在點擊時卻會出現無法順利顯示/關閉的情況。
原因便在於 setState 在 update 時本身是非同步的,導致 update 時留下的微小「縫隙」,進而產生切換狀態時造成的無預期 bug。
而將程式碼修改成以下便恢復正常了:

const [show, setShow] = React.useState(false)
const handleClick = ()=>{
    setShow(prev => !prev)
}
return (<div>
        <button onClick={handleClick}>button</button>
    </div>
);

React 官方文件中提到

如果新的 state 是用先前的 state 計算出,你可以傳遞一個 function 到 setState。該 function 將接收先前的 state,並回傳一個已更新的值。


另一個案例則是最近在工作上發現專案中某些緊鄰著 setState 做的行為產生的 bug ,我們先看下底下的程式碼,看結果之前可以先試著想一下 console 出來的結果會長怎樣:

const [state , setState] = React.useState(0)

React.useEffect(() => {
    setState(1)
    console.log("1 : " , state)
} , [])

console.log('2 : ' , state)

公佈答案:

// 2 : 0
// 1 : 0
// 2 : 1

上面只是簡化的示意版,然在專案中的使用情境主要是:

在接收到某些資料時,要改變畫面上的 state , 並且用新的資料去執行一次某個 function
而該 function 預設會用到該 state 去打 Api 獲取資料

把情境帶回去上面的程式碼後就可以發現,我們原先預期 useEffect 內的 function 會使用我們 setState 後的新值,然而事實並不如此,這個 effect 內所做的事情,總是會不如預期。

於是乎我們在程式碼中做了一點修改,把要執行的 function 加入可選 params,達到在沒有傳入參數時預設拿 state 來用; 在特定情境時,強制傳入參數來確保該 function 執行時資料的正確性。

實際上會有點像這樣:

const [state , setState] = React.useState(0)

const myFunction = ({ param = state } : { param? : number }) => {
    console.log(param)
}

React.useEffect(() => {
    // 符合某些條件時執行
    setState(1)
    myFunction({ param : 1 })
} , [])

當然真實專案會遠比範例還要複雜許多,也因為隨著複雜度的提升,我們常常會忽略掉這些最基本的細節。
而這些小細節往往就是出 bug 的關鍵,寫這篇同時也是謹惕自己。

唯有做好細節,才能寫出更完善的程式碼。