今天同事突然問到一個問題:為什么我請求數(shù)據(jù)后,多次調(diào)用setState()如贷,每一個都會re-render一次呢陷虎?不應(yīng)該是一次批量更新嗎到踏?react不是批量更新嗎,為什么會失效了呢尚猿?react是不是需要開啟批量更新才能生效呢窝稿?下面就記錄一下關(guān)于這個問題的討論:
什么是批量更新?
批量更新是React團(tuán)隊為了有更好的性能凿掂,將多次渲染合并成為一次批量渲染伴榔。React只會渲染一次即使你改變了兩次state中的值,會進(jìn)行批量渲染庄萎。
import { unstable_batchedUpdates } from 'react-dom'
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // Does not re-render yet
setFlag(f => !f); // Does not re-render yet
// React will only re-render once at the end (that's batching!)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
關(guān)于react17中的更新處理
(1) 在React17中潮梯,如果是內(nèi)部的事件處理昼钻,React會進(jìn)行批量更新取刃。
批量更新帶來了更好的性能绞绒,因為它避免了不必要的重復(fù)渲染(re-dender)敌买。state只進(jìn)行了一次批量更新描扯。這就相當(dāng)于當(dāng)你就點(diǎn)了一個菜的時候纵柿,餐館的服務(wù)員不會立刻跑到廚房告訴后廚却汉,而是等你點(diǎn)完所有的菜之后廊酣,才會把菜單提交上去锉罐。
(2)然而帆竹,React并沒有在所有的更新事件中都采用批量更新的方式。
比如脓规,當(dāng)你異步請求數(shù)據(jù)之后栽连,需要更新state 狀態(tài)等,React并沒有采用批量更新的方式進(jìn)行更新侨舆,react開發(fā)者認(rèn)為秒紧,在異步環(huán)境下,setState更新是不可控的挨下,所以采取了同步更新的方式(也就出現(xiàn)了文章開頭出現(xiàn)的現(xiàn)象)熔恢。
這是因為React只會在瀏覽器事件中采用批處理(像點(diǎn)擊事件),但是我們更新state的狀態(tài)是在fetch的回調(diào)函數(shù)中進(jìn)行的臭笆。
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 17 and earlier does NOT batch these because
// they run *after* the event in a callback, not *during* it
setCount(c => c + 1); // Causes a re-render
setFlag(f => !f); // Causes a re-render
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
React 17不會在它不能控制的事件中采用批量更新叙淌。 在React 18之前,我們只在React能控制的事件處理程序中批量更新愁铺。默認(rèn)情況下鹰霍,promise、setTimeout茵乱、原生事件處理程序或任何其他事件內(nèi)部的更新不會在React中批量執(zhí)行茂洒。
(3)如何手動批量更新
如何在異步環(huán)境下,開啟批量更新模式呢似将?React-Dom 中提供了批量更新方法 unstable_batchedUpdates获黔,可以去手動批量更新
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setTimeout(() => {
unstable_batchedUpdates(()=>{
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
})
}, 1000);
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
什么是自動批量更新
React18中用createRoot代替了render,所有的更新都會是自動的批量更新在验,無論他們是從哪里產(chǎn)生的玷氏。
這意味著settimesout,promise,原生的事件處理或者其他的事件都會被當(dāng)成React內(nèi)部事件進(jìn)行批量更新。這將會減少re-render的次數(shù)腋舌,也會帶來更好的性能盏触。
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 18 and later DOES batch these:
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
React 18中的批量更新
在React18中使用createRoot都會采用批處理更新的方式
沒有使用createRoot不會進(jìn)行批處理更新
合成事件
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout、setInterval
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
異步請求
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
})
原生事件
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
在react18使用了createroot的環(huán)境下块饺,上述幾種方式都會產(chǎn)生一樣的效果(都是自動批量更新)赞辩。
總結(jié)
之前的React版本分成了半自動批量更新和手動批量更新。手動批量更新就是React內(nèi)部提供了batchedUpdates方法授艰,用于在batchedContext上下文環(huán)境中執(zhí)行回調(diào)函數(shù)辨嗽。它是將原先的上下文環(huán)境保存起來,然后通過或等于的操作淮腾,代表當(dāng)前屬于BatchedContext上下文環(huán)境糟需,函數(shù)執(zhí)行完成之后再恢復(fù)成之前的上下文環(huán)境。開發(fā)者可以手動調(diào)用batchedUpdates方法按需合并更新谷朝,所以稱為手動批量更新洲押。
V18版本之前會在合適的時機(jī)使用batchedUpdates方法執(zhí)行回調(diào)函數(shù)。legacy 模式在合成事件中有自動批處理的功能圆凰,但僅限于一個瀏覽器任務(wù)杈帐。非 React 事件想使用這個功能必須使用 unstable_batchedUpdates。在 blocking 模式和 concurrent 模式下专钉,所有的 setState 在默認(rèn)情況下都是批處理的挑童。會在開發(fā)中發(fā)出警告。
因為更改batchedContext的環(huán)境是同步的跃须,當(dāng)異步事件執(zhí)行的時候炮沐,早已經(jīng)跳出來batchedContext的執(zhí)行環(huán)境了,executionContext已經(jīng)不包含batchUpdates環(huán)境了回怜,所以異步觸發(fā)的更新是不能自動批量更新的大年。
React18之后,全部采用了自動批量更新的模式玉雾。交給了schedule階段的調(diào)度策略完成了