你覺得Hooks這一點煩嗎?

源 / 文/ 卡頌
大家好,我卡頌。
昨天一個小伙伴發(fā)了一個Demo給我,讓我解釋下原因。
我一看,好家伙,小小一個Demo,知識點囊括了:
Hooks的閉包問題state是如何組裝的
相信看完這個Demo,對函數(shù)組件會有更深的認識。

讓人懵逼的Demo
Demo包含一個按鈕、一個列表。
<div className="App">
<button onClick={add}>Add</button>
{list.map(val => val)}
</div>
點擊按鈕,調(diào)用add方法,向列表中插入一項:
let i = 0;
export default function App() {
const [list, setList] = useState([]);
const add = () => {
// ...
};
return (
<div className="App">
<button onClick={add}>Add</button>
{list.map(val => val)}
</div>
);
}
顯示效果:

燒腦的地方在于,調(diào)用add方法插入的是一個「點擊后會調(diào)用 add 方法的按鈕」:
const add = () => {
setList(
list.concat(
<button
key={i}
onClick={add}>
{i++}
</button>
)
);
};
點擊Add按鈕7下后的顯示效果:

那么問題來了,點擊帶數(shù)字按鈕(會調(diào)用和點擊Add按鈕一樣的add方法)后會有什么效果呢?

state的組裝和閉包問題
如果你認為會插入一個新按鈕:

那就錯了。

正確答案是:點擊對應(yīng)按鈕后list長度變?yōu)?strong style="color: rgb(145, 109, 213);">「按鈕對應(yīng)數(shù)字 + 1」,且最后一項的數(shù)字為「點擊前最大數(shù)字 + 1」。
比如,點擊前最大數(shù)字為6

如果點擊 0,list長度變?yōu)?/span>0 + 1 = 1,且最后一項為6 + 1 = 7:

如果點擊 2,list長度變?yōu)?/span>2 + 1 = 3,且最后一項為6 + 1 = 7:

這是兩個因素共同作用的結(jié)果:
Hooks的閉包問題state是如何組裝的
原因分析
再來看看add方法:
const add = () => {
setList(
list.concat(
<button
key={i}
onClick={add}>
{i++}
</button>
)
);
};
button點擊后調(diào)用add,所以會基于add所屬上下文(App函數(shù))形成閉包,閉包中包括:
add
list
setList

i屬于module級作用域,不在該閉包內(nèi)
其中list與setList來自于useState調(diào)用后的返回值:
const [list, setList] = useState([]);
一種常見的認知誤區(qū)是:多次調(diào)用useState返回的list是同一個引用。
事實上,每次調(diào)用useState返回的list都是基于如下公式計算得出的:
基準state + update1 + update2 + ... = 當前state
所以是一個全新的對象。
如果你想了解更多
update、state計算的細節(jié),參考React技術(shù)揭秘[1]
當首屏渲染時:
App組件首次render創(chuàng)建
list = []<button onClick={add}>Add</button>依賴add,形成閉包,閉包中的list = []
接下來,點擊Add按鈕:
調(diào)用
add方法,該方法來自于首屏渲染創(chuàng)建的閉包add方法中依賴的list來自于同一個閉包,所以list = []<button key={i} onClick={add}>{i++}</button>依賴add,形成閉包,閉包中的list = []
所以,對于按鈕0,

任何時候點擊他實際上執(zhí)行的都是:
setList(
[].concat(
<button key={i} onClick={add}>{i++}</button>
)
);
那么如何修復(fù)這個問題呢,也很簡單,將setList的參數(shù)改為函數(shù)形式:
// 之前
setList(list.concat(<button key={i} onClick={add}>{i++}</button>));
// 之后
setList(list => list.concat(<button key={i} onClick={add}>{i++}</button>));
函數(shù)參數(shù)中的list來自于Hooks中保存的list,而不是閉包中的list。
總結(jié)
由于Hooks總是在組件render時才會計算新狀態(tài),這為Hooks帶來比較重的心智負擔。
相比而言,采用「細粒度更新」實現(xiàn)的Hooks(比如VUE的Composition API)可以實時更新狀態(tài),操作起來更符合直覺。
在使用Hooks過程中,你有沒有遇到類似的頭疼問題呢?
參考資料
React技術(shù)揭秘:https://react.iamkasong.com/state/mental.html#%E5%90%8C%E6%AD%A5%E6%9B%B4%E6%96%B0%E7%9A%84react
推薦閱讀


中國唯一一座沒有高樓大廈的新一線城市,也太佛了吧

國內(nèi)有程序員電視劇了,結(jié)果看了一分鐘,就吐了...

華為最美小姐姐被外派墨西哥后...
END


頂級程序員:topcoding
做最好的程序員社區(qū):Java后端開發(fā)、Python、大數(shù)據(jù)、AI
一鍵三連「分享」、「點贊」和「在看」
