React 的 useState 機制
前言
說來令人慚愧,筆者已經用 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])
};
基本特性 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])
}
那我要如何在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])
}
以上就是本篇的分享,若有任何疏漏之處,不吝指正 ~