React之17/18中的批量更新

今天同事突然問到一個問題:為什么我請求數(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)度策略完成了

參考文章:https://developer.aliyun.com/article/1257887

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翔试,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子复旬,更是在濱河造成了極大的恐慌垦缅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驹碍,死亡現(xiàn)場離奇詭異壁涎,居然都是意外死亡凡恍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門怔球,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚼酝,“玉大人,你說我怎么就攤上這事竟坛∶龉” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵担汤,是天一觀的道長涎跨。 經(jīng)常有香客問我,道長崭歧,這世上最難降的妖魔是什么隅很? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮率碾,結(jié)果婚禮上外构,老公的妹妹穿的比我還像新娘。我一直安慰自己播掷,他們只是感情好审编,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歧匈,像睡著了一般垒酬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上件炉,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天勘究,我揣著相機(jī)與錄音,去河邊找鬼斟冕。 笑死口糕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的磕蛇。 我是一名探鬼主播景描,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秀撇!你這毒婦竟也來了超棺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤呵燕,失蹤者是張志新(化名)和其女友劉穎棠绘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氧苍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年夜矗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片让虐。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡紊撕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澄干,到底是詐尸還是另有隱情,我是刑警寧澤柠傍,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布麸俘,位于F島的核電站,受9級特大地震影響惧笛,放射性物質(zhì)發(fā)生泄漏从媚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一患整、第九天 我趴在偏房一處隱蔽的房頂上張望拜效。 院中可真熱鬧,春花似錦各谚、人聲如沸紧憾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赴穗。三九已至,卻和暖如春膀息,著一層夾襖步出監(jiān)牢的瞬間般眉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工潜支, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甸赃,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓冗酿,卻偏偏與公主長得像埠对,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裁替,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容