摘要
在React中,如果涉及到了多次setState,組件render幾次。setState是同步的還是異步的。這是一個很常見的面試題。
而本篇文章,就是主要實現(xiàn)React中,對于這部分的性能優(yōu)化,我們稱之為批處理。例如當(dāng)我有下面的JSX。
const root = document.querySelector('#root');
function App() {
const [num, setNum] = useState(0)
const click1 = () => {
setNum(num + 1)
setNum(num + 2)
setNum(num + 3)
}
return jsx("div", {
onClick: click1,
children: num
});
}
ReactDOM.createRoot(root).render(<App />)
對于當(dāng)前的點擊事件來說,只有最后的setNum(num + 3)是有效的。
但是在我們之前的實現(xiàn)中,對于這種連續(xù)三次setState,我們的代碼就要處理三次,就要經(jīng)過三次beginWork,completeWork,commitWork。
那我們能不能實現(xiàn)出一種方式,對于這種多次setState,最終之做最后一次處理。
1.修改update相關(guān)邏輯
目前,在我們的代碼里,能夠讓組件更新的方式,就是通過setState,觸發(fā)更新。而updateQueue就是用來保存更新的內(nèi)容。
function enqueueUpdate(updateQueue, update) {
updateQueue.shared.pending = update
}
之前,在enqueueUpdate方法里,我們是直接進(jìn)行賦值的。這沒什么問題,因為之前每次賦值之后都會更新一下。
但是現(xiàn)在我們希望,最后只更新一次的話。我們就需要一個數(shù)據(jù)結(jié)構(gòu),可以保存多次更新的內(nèi)容。這里使用的是鏈表結(jié)構(gòu),但在真正的React源碼中,使用的是環(huán)形鏈表。
function enqueueUpdate(updateQueue, update) {
// updateQueue.shared.pending = update
let pending = updateQueue.shared.pending;
if(pending === null) {
updateQueue.shared.pending = update
}else{
while(pending.next != null) {
pending = pending.next;
}
pending.next = update
pending = pending.next;
}
}
OK,修改完之后,我們調(diào)用了三次setState,那么updateQueue中保存的應(yīng)該是一個鏈表了。
在之前的processUpdateQueue方法里,也是直接更新就完了。但是現(xiàn)在updateQueue的結(jié)構(gòu)發(fā)生了變化,所以對于processUpdateQueue,更新邏輯也要改變。
function processUpdateQueue(baseState, pendingUpdate) {
const result = {
memoizedState: baseState
}
if(pendingUpdate.next === undefined) {
const action = pendingUpdate.action;
//setState(() => {}) 傳入方法
if(typeof action === 'function'){
result.memoizedState = action(baseState);
}else {
//setState()
result.memoizedState = action;
}
return result
}
while(pendingUpdate != null) {
const action = pendingUpdate.action;
//setState(() => {}) 傳入方法
if(typeof action === 'function'){
baseState = action(baseState);
}else {
//setState()
baseState = action;
}
pendingUpdate = pendingUpdate.next;
}
result.memoizedState = baseState;
return result;
}
我們需要遍歷pending,將所有的更新內(nèi)容返回。
2.修改workLoop循環(huán)
我們想一下,之前觸發(fā)更新后,執(zhí)行的機(jī)制是什么樣子的。
- 更新updateQueue
- 拿到更新的updateQueue,掛載Hook上
- 執(zhí)行workLoop
也就是說,每次workLoop都只能拿到當(dāng)前更新的內(nèi)容。
如果我希望在第一次workLoop就可以拿到所有的更新內(nèi)容,并且取消后面的workLoop。
有什么方法呢?微任務(wù)?。。。?/p>
我們可以將執(zhí)行workLoop的過程放在微任務(wù)里,這樣執(zhí)行workLoop的時候,updateQueue的鏈表已經(jīng)生成。
同時我們用一個標(biāo)志位,取消后面的workLoop執(zhí)行。
當(dāng)workLoop執(zhí)行完成后,再將標(biāo)志位置反。
let isFinished = false;
export function syncWorkLoop(root,hostRootFilber) {
if(isFinished) {
return;
}
Promise.resolve(null).then(() => {
wookLoop(root,hostRootFilber);
})
isFinished = true;`在這里插入代碼片`
}
const wookLoop = (root,hostRootFilber) => {
//其他代碼。。。。。
isFinished = false;
}
3.修改filberHook
最后,我們只要在filberHook中觸發(fā)的邏輯里,替換workLoop即可:文章來源:http://www.zghlxwxcb.cn/news/detail-666589.html
function disaptchState(filber, hook, action) {
const update = createUpdate(action);
enqueueUpdate(hook.updateQueue, update);
workUpdateHook = hook;
syncWorkLoop(filber.return.stateNode);
}
通過這種方式,當(dāng)我執(zhí)行多次setState,最終只會render一次。文章來源地址http://www.zghlxwxcb.cn/news/detail-666589.html
到了這里,關(guān)于React源碼解析18(11)------ 實現(xiàn)多次setState的批處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!