React 的 useState 機制

分享 React useState 的基本特性

邱秉誠
7 min readApr 27, 2024

前言

說來令人慚愧,筆者已經用 react 框架開發一段期間了,但始終沒有真正理解 useState 這麼基本重要的原理,今天用幾個例子來梳理 useState 的兩個基本特性,第一是狀態的更新是異步執行,第二是每個函示空間有自己的狀態變數版本。

基本介紹

useState 是在組件中添加狀態的方法,比如當我在組件內進行以下的宣告,意味著我宣告一個名為imgList 的狀態變數,並且我可以使用 setImgList 函示來更改此狀態變數。

const App = () => {
const [imgList, setImgList] = useState([])
}

這裡要注意的是,調用由 useState 提供的更新函數 (setImgList) 時,React 會將新狀態與當前狀態進行比較,如果是同一個參考,則不會進行重新渲染。

基本特性 1 — 狀態的更新是異步執行 (Asynchronous execution)

新手在使用useState最困惑的地方莫過於,為何我調用更新狀態函數卻沒有立即更新狀態? 以下方例子來說明,我新增一個updatedImgList變數,其值為現有的imgList 再添加一張新的 Image,然後調用setImgList來更新狀態,並在隨後立即使用console.log(imgList),結果卻發現在console上打印出的是一組空陣列,這是因為狀態的更新是異步執行,react 會將所有待更新的狀態變數放在一隊列中,等待下一個渲染 (render) 週期再進行實際的狀態更新,因此同一次函式調用中訪問的都是當前版本的狀態變數。

const App = () => {
const [imgList, setImgList] = useState([])

useEffect(() => {
const updatedImgList = [...imgList]
updatedImgList.push(`Image${imgList.length}`)
setImgList(udpatedImgList)
console.log(imgList)
}, [])
}

useEffect是一種 Hook,第一個參數是執行的函示,第二參數是依賴變數陣列,當指定的依賴變數任一發生改變,該 Hook 會執行其定義的函示操作。而在上面的例子中,我們將依賴變數設為空陣列,這表示該函示只會在首次 render 後執行一次。

我們可以使用 useEffect 的 Hook來檢查 imgList 狀態變數更新後的實際值,而從 console 上打印出來的訊息確實有被成功更新。

備註:useEffect在 react 中不是作為檢查渲染值的用途,這裡僅是以此展示有成功更新變數。

  const App = () => {
console.log("APP render")
const [imgList, setImgList] = useState([])

useEffect(() => {
const udpatedImgList = [...imgList]
udpatedImgList.push(`Image${imgList.length}`)
setImgList(udpatedImgList)
console.log("render前的值 imgList:", imgList)
}, [])

useEffect(() => {
console.log("render後的最新值 imgList:", imgList)
}, [imgList])
};
利用 useEffect 來觀察 render 前後的 imgList 狀態變化

基本特性 2 — 每個函示空間有特定的狀態變數版本

這個概念有點抽象,useState狀態變數並不是一個唯一的參考,在每個函示空間內都有特定的版本,用以下的例子做說明,我使用setInterval每3秒鐘調用createRegulary函示,試圖定時增加一張 Image ,結果發現在函示內部,imgList狀態變數永遠都是一個空陣列。因為你每次調用的都是同一個最初的createRegulary函示,而這個最初的函示內部的imgList狀態變數就是一個空陣列 ,這等於每次都在一個空陣列上,去新增一張 Image。

備註:這裡我們依然使用useEffect觀察 本次 render 後的當前版本值。

const App = () => {
const [imgList, setImgList] = useState([])

function createRegulary() {
const updatedImgList = [...imgList]
updatedImgList.push(`Image${imgList.length}`)
setImgList(updatedImgList)
console.log("imgList in the createRegulary function", imgList)
}

useEffect(() => {
var intervalId = setInterval(createRegulary, 3000);
}, [])

useEffect(() => {
console.log("up-to-date imgList: ", imgList)
}, [imgList])
}
函數內的imgList狀態變數永遠都是最初那個空陣列版本

那我要如何在createRegulary函示內部得到全局上最新版本的狀態變數?在react 的 更新狀態函數中有提供一種函數版本的方式,可以讓你取得最新版本的狀態變數,以下面的例子來做說明,prevImgList就是代表全局上最新版本的imgList狀態變數,我們用該最新版本來進行新增的動作,以確保不是重複沿用舊的狀態。這樣子就能得達到定時新增圖片的目的,但此時需要特別留意,createRegulary函示內部的imgList仍是最初那個版本,如圖所示。

setImgList(prevImgList => {
const updatedImgList = [...prevImgList, `Image${prevImgList.length}`];
return updatedImgList;
});
const App = () => {
const [imgList, setImgList] = useState([])

function createRegulary() {

setImgList(prevImgList => {
const updatedImgList = [...prevImgList, `Image${prevImgList.length}`];
return updatedImgList;
});

console.log("imgList in the CreateRegulary functoin", imgList)
}

useEffect(() => {
var intervalId = setInterval(createRegulary, 3000);
}, [])

useEffect(() => {
console.log("up-to-date imgList: ", imgList)
}, [imgList])

}

以上就是本篇的分享,若有任何疏漏之處,不吝指正 ~

--

--

邱秉誠
邱秉誠

Written by 邱秉誠

畢業於台大工業工程所,目前任職於台積電。

No responses yet