2023-12-26
【React 新手村】千萬不要這樣使用useState!

原文發表於 Medium。
下面的案例有個錯誤,您可以把它找出來嗎?
import React, { useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default ExampleComponent;
答案是 setCount(count + 1) 這段寫錯了。
正確使用Setter
對於useState有深入的了解之後,我才知道,使用useState的setter時,若要在當前值的基礎上變更成新值,我們應該要使用下面這個形式
setCount(prevCount => prevCount - 1);
在setter中使用Arrow Function入參,useState會自動把state的當前值再次入參給這個Arrow Function,接著就看我們要return什麼東西了。在這樣的形式中,我們才能真正確認setter抓到的值是真的當前值。
但很多網路上的教學或者一些文章中使用useState都不是使用這樣的形式,我個人猜想可能是因為方便,因為若不是在複雜、頻繁地變換state之下,基本上不會有問題。(例如在這個案例中,這個錯誤就不會造成問題。)
但不得不說,這確實是個隱憂,可能會在未來成為一個不定時炸彈。下面就給各位一個使用錯誤的useState setter時會出錯的案例:
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
//不正確的按鈕
const incrementIncorrect = () => {
setCount(count + 1);
setCount(count + 1);
};
//正確的按鈕
const incrementCorrect = () => {
setCount((prevCount) => prevCount + 1);
setCount((prevCount) => prevCount + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementIncorrect}>不正確按鈕</button>
<button onClick={incrementCorrect}>正確按鈕</button>
</div>
);
}
export default Counter;
在這個組件中,我們設定了兩個按鈕,分別綁定了正確與不正確使用useState的handler。乍看之下,兩個按鈕按了都會讓count值加2,但實際上,不正確的按鈕只會加1,而正確的按鈕才是加2。
哪個當前值?
會出現這樣的Bug,是因為我們使用不正確的用法時,setter會把state當作從外部來的值,若要針對state當前值做連續操作時,每一段操作都只會抓到函式創建時的值,而忽略掉執行時的各項變化過後的值;而若是使用正確的Arrow Function入參的方法,我們就可以針對useState內部的真實當前值進行操作。我們用下方的code來說明更容易理解:
const incrementIncorrect = () => {
setCount(count + 1); //0+1
setCount(count + 1); //0+1
setCount(count + 1); //0+1
setCount(count + 1); //0+1
setCount(count + 1); //0+1
//count值以最後一個setter為準,因此無論使用多少次steCount,得到的新值還是1。
};
const incrementCorrect = () => {
setCount((prevCount) => prevCount + 1); //0+1
setCount((prevCount) => prevCount + 1); //1+1
//count值以最後一個setter為準,因此是2。
};
這邊跟我過去談過的閉包概念有關。在使用handler時,都會形成閉包,當我們使用錯誤的方法時,閉包就會把count的值先「包」起來,所以count從閉包創立之後就不會變了。然而在使用Arrow Function時,我們並沒有把count值包死,而且透過內部的入參,將真正的當前值帶入到每次的setter呼叫中。(傳送門:閉包的概念【 JS小劇場 】-Closure)
為了避免在未來踩到坑,我們還是堅持用Arrow Function入參,透過正確的方式來操作useState,會是比較保險的方法!若有補充或指正的知識,還請各位先進在留言區指教唷!
希望大家在code路上天天有所得,我們一起成長並進!