React手冊(cè) Hooks 之 useState
描述
? ? React 官網(wǎng)對(duì) useState 的描述原文
useState?
is a React Hook that lets you add a state variable to your component.
useState?
是一個(gè) React Hook,可讓您向組件添加狀態(tài)變量。
? ? 這是一個(gè)非常常用且基礎(chǔ)的 Hook, 組件的渲染, 操作, 功能, 樣式, 大多數(shù)時(shí)候都與之有關(guān).
????
????接口定義:
場(chǎng)景
????當(dāng) React?渲染一個(gè)函數(shù)組件的時(shí)候, 會(huì)執(zhí)行這個(gè)函數(shù), 假如直接在函數(shù)中定義局部變量,?那么就會(huì)產(chǎn)生兩個(gè)問題:?
每次渲染組件的時(shí)候, 這個(gè)變量都會(huì)被重新聲明;
如果修改了局部變量, 組件感知不到變化從而無法觸發(fā)重新渲染;
???因此為了讓組件能有狀態(tài), 同時(shí)保持組件是純函數(shù),所以需要?useState?創(chuàng)建和操作狀態(tài).
參數(shù)
initialState:?(() => any) |?any
任意類型的初始化狀態(tài)值或一個(gè)返回任意類型的初始化狀態(tài)值的無參純函數(shù), initialState?能夠接受任意類型的參數(shù), 不過如果是函數(shù), 會(huì)有特殊效果
返回
????返回一個(gè)特定格式的數(shù)組 [any, Dispatch], 其中第一項(xiàng)是當(dāng)前狀態(tài), 它的值與你傳入的初始值相等, 第二項(xiàng)是修改這個(gè)狀態(tài)并觸發(fā)重新渲染的 set?函數(shù).
????set 函數(shù), 也就是?Dispatch, 也有兩種傳參方式, 可以傳入要設(shè)置的下一個(gè)狀態(tài), 或基于當(dāng)前狀態(tài)計(jì)算下一個(gè)狀態(tài)的函數(shù), set 函數(shù)沒有返回值.
????set?函數(shù)傳入的更新方法應(yīng)該是純函數(shù),?它將會(huì)被?React?維護(hù)進(jìn)該狀態(tài)的更新隊(duì)列, 在下一次更新時(shí), 會(huì)依次執(zhí)行更新隊(duì)列中的函數(shù), 前一次更新函數(shù)的返回值, 將會(huì)當(dāng)作參數(shù)傳給后一個(gè)更新函數(shù).
用法1
????給函數(shù)組件增加狀態(tài), 慣例是按照像[ something, setSomething?] 這樣使用數(shù)組解構(gòu)的方式命名狀態(tài).
????使用?setName('Robin'); 方式更新狀態(tài), 但是 set 方法只會(huì)影響下一次 useState 渲染調(diào)用時(shí)返回的結(jié)果, 并不會(huì)更改已經(jīng)執(zhí)行代碼的當(dāng)前狀態(tài).
用法2
? ? 根據(jù)之前的狀態(tài)更新狀態(tài), 假如?age 是 1, 然后我們連續(xù)調(diào)用三次 setAge(age + 1):?
????因?yàn)?set 函數(shù)不會(huì)改變已經(jīng)運(yùn)行中的狀態(tài), 要解決這個(gè)問題, 可以給 set 函數(shù)傳入一個(gè)更新函數(shù):?
????當(dāng) set 函數(shù)收到一個(gè)更新函數(shù)時(shí), React?會(huì)把更新函數(shù)存放進(jìn)一個(gè)更新隊(duì)列,?在下一次渲染時(shí),?React?會(huì)以相同的順序調(diào)用這些更新函數(shù),?并把最終值存儲(chǔ)為當(dāng)前狀態(tài).
????按照慣例, 更新函數(shù)中的參數(shù)(也叫掛起狀態(tài)?pending state), 我們認(rèn)為是抽象的, 命名的時(shí)候需要和真實(shí)的狀態(tài)做出區(qū)分, 所以一般使用狀態(tài)變量的首個(gè)字母來作為參數(shù)的命名, 比如使用 a?來作為 age 掛起狀態(tài)參數(shù), 或者其他諸如?prevAge?等, 你覺得合適準(zhǔn)確的命名方式.
用法3
? ? 更新狀態(tài)中的對(duì)象和數(shù)組,?在 useState 中, 可以傳入對(duì)象和數(shù)組, 但是需要注意的是, 作為狀態(tài)的對(duì)象應(yīng)該是只讀的, 你應(yīng)該替換它, 而不是直接修改, 假如有一個(gè) form 對(duì)象作為狀態(tài):
????. . . 的解構(gòu)賦值是一種淺操作, 只會(huì)復(fù)制第一層, 所以速度非??? 同時(shí)也意味著你如果是多層嵌套的對(duì)象, 就不得不多次使用這種操作.
? ? 這樣的操作很繁瑣, 但是有效, 同時(shí)?React 給出了兩種處理多級(jí)對(duì)象的方案:
一種是避免對(duì)象的嵌套,?使用對(duì)象引用賦值以上面的對(duì)象為例
? ? artwork 和 person 可以被分成兩個(gè)獨(dú)立對(duì)象分開維護(hù), 他們之間的關(guān)系可以使用引用賦值的方式建立.
第二種是使用 Immer 庫
????嘗試 Immer:
????使用 useImmer 替換 useState
用法4
????避免重新創(chuàng)建初始狀態(tài), 如果在?useState 中的初始值是通過函數(shù)計(jì)算得出的, 那么就需要把函數(shù)結(jié)果傳給?useState, 像這樣:
????盡管 createState() 函數(shù)的返回值只用做初始化組件時(shí)使用, 但是在實(shí)際渲染中, 每次渲染 List?組件時(shí), createState() 都會(huì)被執(zhí)行, 這會(huì)造成浪費(fèi).
? ? 要避免這種浪費(fèi), 需要像下面這個(gè)樣改寫
?? ? 這里沒有吧?createState 的執(zhí)行結(jié)果傳給 useState, 而是把?createState 函數(shù)本身當(dāng)作初始化函數(shù)傳給 useState, 這樣?React 只會(huì)在初始化階段調(diào)用?createState 函數(shù), 在實(shí)際執(zhí)行過程中, 兩種寫法效果一樣, 但是傳遞函數(shù)本身的方式性能更好.
用法5
????重制組件狀態(tài), 使用 useState 來控制 key, 當(dāng)你在渲染列表時(shí), 你經(jīng)常會(huì)用到?key, 不過它還有另外一個(gè)功能, 那就是當(dāng)你主動(dòng)修改?key?時(shí), React 會(huì)重新創(chuàng)建組件, 組件內(nèi)的狀態(tài)也會(huì)被重置.
????當(dāng) version 發(fā)生變化時(shí), Form 組件就會(huì)被重新創(chuàng)建, 從而重置狀態(tài).
用法6
????狀態(tài)修改的歷史, 也就是獲取本次狀態(tài)的前一次狀態(tài), 這是一個(gè)少數(shù)場(chǎng)景, 當(dāng)你正常使用 set 函數(shù)的時(shí)候, 你很輕易的可以拿到當(dāng)前狀態(tài)和下一次狀態(tài), 但是如果狀態(tài)的變更來自外部, 通過 props 傳入進(jìn)來的, 那就無法獲取狀態(tài)變更前的值.
????解決的方式就是在接收?props 的組件內(nèi)部, 使用 useState 對(duì)狀態(tài)的變化趨勢(shì)進(jìn)行存儲(chǔ):
????在判斷?prevCount !== count 中執(zhí)行?setPrevCount(count), 這一步是必須的, 通常在組件渲染過成中直接調(diào)用 set 函數(shù)都是不允許的, 會(huì)收到一個(gè) Too many re-renders. 的錯(cuò)誤, 所以需要使用判斷控制.
????觀察打印信息之后, 會(huì)發(fā)現(xiàn)一個(gè)現(xiàn)象, 組件?CountLabel 內(nèi)的日志打印了兩次, 而其子組件 Children 內(nèi)的日志只打印了一次, 為什么父組件渲染兩次, 而子組件只渲染一次? 因?yàn)楦附M件的兩次渲染, 有一次是在渲染過成中調(diào)用 set 函數(shù)引起的,?React 處理這種渲染的時(shí)候, 會(huì)在函數(shù)執(zhí)行完畢之后并在開始渲染之前, 再次重新渲染組件, 這樣就會(huì)避免子組件渲染兩次, 頭一次的計(jì)算結(jié)果將會(huì)被丟棄, 你也可以在頭一次函數(shù)執(zhí)行的過程中提前 return, 盡快開始重新渲染.
? ??
用法7
????設(shè)置函數(shù)作為一個(gè)狀態(tài), 如果你想把一個(gè)函數(shù)作為狀態(tài)進(jìn)行存儲(chǔ):
????你會(huì)發(fā)現(xiàn) someFunction 會(huì)被執(zhí)行, 這和預(yù)期不符, 因?yàn)?useState 會(huì)認(rèn)為你傳入的是一個(gè)初始化函數(shù), 將其執(zhí)行, 并將返回值進(jìn)行存儲(chǔ), set 函數(shù)同樣認(rèn)為 someOtherFunction 是一個(gè)計(jì)算下一狀態(tài)的函數(shù), 將其執(zhí)行, 并把返回值作為狀態(tài)更新, 正確的將函數(shù)作為狀態(tài)的寫法如下:
????將這個(gè)函數(shù)本身作為返回值, 放在一個(gè)函數(shù)中, 傳給 useState 和 set 函數(shù).
總結(jié)
useState 返回一個(gè)數(shù)組, [count, setCount];
useState 可以傳入初始化函數(shù), 只在初始化調(diào)用;
set 函數(shù)可以傳入一個(gè)函數(shù), 參數(shù)是當(dāng)前的掛起狀態(tài), 并將返回值作為新狀態(tài)更新;
當(dāng)狀態(tài)是對(duì)象時(shí), 需要使用 . . .?解構(gòu)賦值, 對(duì)狀態(tài)進(jìn)行直接替換, 或使用 Immer 庫;
使用 useState 控制組件的 key 時(shí), 可以讓組件重新創(chuàng)建, 從而重置狀態(tài);
在組件渲染過程中調(diào)用 set 函數(shù)是不允許的, 需要使用判斷控制, 此時(shí)組件函數(shù)會(huì)執(zhí)行兩次,? 第一次的結(jié)果會(huì)被丟棄, 并且都是在真實(shí)渲染之前, 防止子組件的多余渲染;
把函數(shù)作為狀態(tài)時(shí), 需要使用另一個(gè)函數(shù)將其返回: useState(() => someFunction), 否則會(huì)被當(dāng)作初始化函數(shù)使用.