useEffect和useLayoutEffect區(qū)別

官方解釋

官方解釋鸳粉,這兩個(gè)hook基本相同闷盔,調(diào)用時(shí)機(jī)不同弯洗,請(qǐng)全部使用useEffect,除非遇到bug或者不可解決的問題逢勾,再考慮使用useLayoutEffect。還舉了個(gè)例子藐吮,譬如你想測(cè)量DOM元素時(shí)候溺拱,使用useLayoutEffect。個(gè)人感覺舉例不恰當(dāng)谣辞,測(cè)試DOM我也完全可以在useEffect中測(cè)量啊迫摔。說(shuō)如果需要在paint前改變DOM,更合適泥从。

我做過(guò)測(cè)試句占,譬如一個(gè)div尺寸是200 * 200,我想改成100 * 100躯嫉,如果寫在useEffect中纱烘,確實(shí)會(huì)造成頁(yè)面抖動(dòng)杨拐,寫在useLayoutEffect中可以避免。

官方解釋鏈接

官方解釋

redux-react-hook 中的妙用

redux-react-hook庫(kù)中有段代碼使用了useLayoutEffect擂啥,用來(lái)避免組件render兩次哄陶。
這里的useIsomorphicLayoutEffect就是useLayoutEffect(因?yàn)閹?kù)要區(qū)分是瀏覽器還是SSR,所以上面做了處理)

    // We use useLayoutEffect to render once if we have multiple useMappedState. 
    // We need to update lastStateRef synchronously after rendering component,
    // With useEffect we would have:
    // 1) dispatch action
    // 2) call subscription cb in useMappedState1, call forceUpdate
    // 3) rerender component
    // 4) call useMappedState1 and useMappedState2 code
    // 5) calc new derivedState in useMappedState2, schedule updating lastStateRef, return new state, render component
    // 6) call subscription cb in useMappedState2, check if lastStateRef !== newDerivedState, call forceUpdate, rerender.
    // 7) update lastStateRef - it's too late, we already made one unnecessary render
    useIsomorphicLayoutEffect(() => {
      lastStateRef.current = derivedState;
      memoizedMapStateRef.current = memoizedMapState;
    });

看得很懵逼哺壶,講了如果用useEffect會(huì)帶來(lái)什么問題屋吨,我模擬了很久終于模擬出來(lái)作者描述的問題(意圖好猜,模擬時(shí)候有個(gè)細(xì)節(jié)很難處理)

模擬場(chǎng)景簡(jiǎn)化

場(chǎng)景

我有一個(gè)數(shù)據(jù)store(對(duì)redux的store)山宾,一個(gè)組件App至扰,組件中使用了useA和useB兩個(gè)自定義hook(這對(duì)應(yīng)兩次調(diào)用redux-react-hook的useMappedState)。

當(dāng)我一個(gè)操作资锰,改變store時(shí)候敢课,去調(diào)用訂閱者即A和B,A和B改變會(huì)觸發(fā)App重新render台妆。這里有個(gè)問題翎猛,A和B都是訂閱者,會(huì)觸發(fā)兩次App重新render接剩,作者想避免切厘,所以會(huì)在use的時(shí)候做下處理,使用useEffect的話懊缺,會(huì)出現(xiàn)bug疫稿,無(wú)法如愿,下面就來(lái)模擬這個(gè)過(guò)程鹃两。

代碼實(shí)現(xiàn)

function App() {
  console.log('%c App render--start-->', 'color:blue')
  const a = useA();
  const b = useB();
  
  function doSomething() {
    // dispatch();
    setTimeout(dispatch, 0)
  }
  console.log('%c App render--end-->', 'color:red')
  return (
    <div>
      <p>a: {a}</p>
      <p>b: 遗座</p>
      <p><button onClick={doSomething}>dispatch</button></p>
      
    </div>
  )
}
function useA() {
  console.log('---a--hook-->')
  const [trigger, setTrigger] = useState(0);
  useEffect(() => {
    console.log('--useA--useEffect-->')
    memoStore = store;
  });
  useEffect(() => {
    const fn = subsriber(() => {
      console.log('--useA--注冊(cè)函數(shù)--->', memoStore, store);
      if(store !== memoStore) {
        setTrigger(Math.random())
      }
    });
    return () => unSubsriber(fn);
  }, []);
  return store;
}
function useB() {
  console.log('---b--hook-->')
  const [trigger, setTrigger] = useState(0);
  useEffect(() => {
    console.log('--useA--useEffect-->')
    memoStore = store
  });
  useEffect(() => {
    const fn = subsriber(() => {
      console.log('--useB--注冊(cè)函數(shù)--->', memoStore, store);
      if(store !== memoStore) {
        setTrigger(Math.random())
      }
    });
    return () => unSubsriber(fn);
  }, []);
  return store;
}

簡(jiǎn)化的redux:

let store = 6;
let memoStore = 6;
const newStore = 8;

const subsriberList = new Set();
function subsriber(fn) {
  subsriberList.add(fn);
  return fn;
}
function unSubsriber(fn) {
  subsriberList.delete(fn)
}
function dispatch() {
  memoStore = store;
  store = newStore;
  subsriberList.forEach(fn => fn())
}

這里有一個(gè)非常重要的關(guān)鍵點(diǎn),就是App組件中的doSometing中俊扳,dispatch一定要寫在setTimeout中途蒋,否則react自動(dòng)幫你優(yōu)化了,模擬不出來(lái)想要的場(chǎng)景馋记。

分析

點(diǎn)擊按鈕時(shí)候号坡,改變了store: 6 -> 8,觸發(fā)了訂閱者自定義hook A和B的訂閱事件梯醒。按理會(huì)觸發(fā)兩次App render宽堆,但是我們做了優(yōu)化,在useA和useB的時(shí)候茸习,會(huì)用新狀態(tài)去覆蓋舊狀態(tài)畜隶,然后在訂閱事件中,會(huì)對(duì)比新老狀態(tài),一致的話籽慢,就不去觸發(fā)自定義hook改變了浸遗,也就不會(huì)觸發(fā)App render了。
但是使用effect的話嗡综,實(shí)際執(zhí)行過(guò)程是這樣的:


使用effect的代碼執(zhí)行流程
控制臺(tái)

可以看到乙帮,App依舊render了兩次,其中主要問題就出在useEffect注冊(cè)的函數(shù)在什么時(shí)候執(zhí)行极景,從流程圖中可以看到察净,其不是在App組件樹 render結(jié)束后立即執(zhí)行的(我也不知道什么時(shí)候執(zhí)行,還請(qǐng)哪位大佬指點(diǎn))盼樟,js會(huì)繼續(xù)執(zhí)行后面的代碼(B的訂閱)氢卡,這個(gè)時(shí)候old=new還沒有執(zhí)行,所以依舊觸發(fā)了第二次App組件render晨缴。

更改useEffect為useLayoutEffect

useA

...
  useLayoutEffect(() => {
    console.log('--useA--useLayoutEffect-->')
    memoStore = store;
  });
...

useB

...
  useLayoutEffect(() => {
    console.log('--useA--useLayoutEffect-->')
    memoStore = store
  });
...
useLayoutEffect執(zhí)行流程
控制臺(tái)

可以看見關(guān)鍵點(diǎn)是译秦,layoutEffect隊(duì)列在組件樹render結(jié)束后,會(huì)立刻同步執(zhí)行(個(gè)人感覺是的)击碗,所以在第一次App render結(jié)束后筑悴,old和new就相同了,在執(zhí)行B訂閱時(shí)候稍途,就會(huì)根據(jù)條件阁吝,不再觸發(fā)App render了。

總結(jié)

// 一定要加setTimeout模擬異步操作械拍,否則實(shí)驗(yàn)不出來(lái)上面的流程的
  setTimeout(()=>{
    renderApp1(); // 一些會(huì)條件性觸發(fā)組件重新render的代碼
    exeLayoutEffectList(); // 組件樹構(gòu)建完畢突勇,會(huì)同步執(zhí)行useLayoutEffect中的代碼
    code1(); // 一些js代碼
    code2(); // 一些js代碼
    // 所有代碼都執(zhí)行完畢后,瀏覽器渲染結(jié)束后坷虑,會(huì)調(diào)用useEffect中的代碼
    // 或者接到下一次組件刷新(re-render)指令甲馋,會(huì)將上一次effect隊(duì)列執(zhí)行完畢。我根據(jù)試驗(yàn)猜的
    exeEffectList(); 
    renderApp2(); // 一些會(huì)條件性觸發(fā)組件重新render的代碼
  }, 0)

主要就是effect和layoutEffect隊(duì)列的執(zhí)行階段迄损,layout會(huì)在組件樹構(gòu)建完畢或者刷新完畢后同步立刻執(zhí)行定躏。effect會(huì)等其他js代碼執(zhí)行完畢后執(zhí)行(或者遇到下一次刷新任務(wù)前)

回過(guò)頭再看react關(guān)于useLayoutEffect的官方文檔:

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Prefer the standard useEffect when possible to avoid blocking visual updates.

  • 和useEffect相同,是指他們都在組件樹構(gòu)建完畢之后執(zhí)行的
  • 但是useLayout是在DOM突變之后立即執(zhí)行的芹敌,突變是指什么共屈?是指類似組件構(gòu)建完畢之后,appendChild(reactTree)這種操作嗎?
  • 可以肯定的是党窜,是在組件樹構(gòu)建完畢后同步執(zhí)行,之后才會(huì)去執(zhí)行后面的js代碼
  • 使用他來(lái)讀取DOM布局尺寸借宵,我倒感覺應(yīng)該是寫成設(shè)定DOM布局尺寸幌衣,這樣可以防抖動(dòng),同步讀取DOM布局尺寸想不懂有什么用
  • useLayoutEffect隊(duì)列中的任務(wù),會(huì)在瀏覽器paint之前執(zhí)行(可以用來(lái)防抖)
  • 盡可能使用useEffect來(lái)避免阻塞視覺更新(見上條豁护,阻礙paint)

吐槽

英語(yǔ)太差哼凯,好多概念模模糊糊的,但是好像看過(guò)國(guó)外文章楚里,也有吐槽react的幾個(gè)概念含糊不清的断部。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市班缎,隨后出現(xiàn)的幾起案子蝴光,更是在濱河造成了極大的恐慌,老刑警劉巖达址,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔑祟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沉唠,警方通過(guò)查閱死者的電腦和手機(jī)疆虚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)满葛,“玉大人径簿,你說(shuō)我怎么就攤上這事∴秩停” “怎么了篇亭?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乳蛾。 經(jīng)常有香客問我暗赶,道長(zhǎng),這世上最難降的妖魔是什么肃叶? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任蹂随,我火速辦了婚禮,結(jié)果婚禮上因惭,老公的妹妹穿的比我還像新娘岳锁。我一直安慰自己,他們只是感情好蹦魔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布激率。 她就那樣靜靜地躺著,像睡著了一般勿决。 火紅的嫁衣襯著肌膚如雪乒躺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天低缩,我揣著相機(jī)與錄音嘉冒,去河邊找鬼曹货。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讳推,可吹牛的內(nèi)容都是我干的顶籽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼银觅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼礼饱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起究驴,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镊绪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后纳胧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镰吆,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年跑慕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了万皿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡核行,死狀恐怖牢硅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芝雪,我是刑警寧澤减余,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站惩系,受9級(jí)特大地震影響位岔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堡牡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一抒抬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晤柄,春花似錦擦剑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至爬坑,卻和暖如春纠屋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盾计。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工巾遭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肉康,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓灼舍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涨薪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骑素,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過(guò)程中的一些閱讀筆記刚夺,個(gè)人覺得該教程講解深入淺出献丑,比目前大...
    leonaxiong閱讀 2,810評(píng)論 1 18
  • 40、React 什么是React侠姑?React 是一個(gè)用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,005評(píng)論 0 1
  • 3. JSX JSX是對(duì)JavaScript語(yǔ)言的一個(gè)擴(kuò)展語(yǔ)法创橄, 用于生產(chǎn)React“元素”,建議在描述UI的時(shí)候...
    pixels閱讀 2,806評(píng)論 0 24
  • [toc] REACT react :1.用來(lái)構(gòu)建用戶界面的 JAVASCRIPT 庫(kù)2.react 專注于視圖層...
    撥開云霧0521閱讀 1,429評(píng)論 0 1
  • 【日精進(jìn)打卡第5天】 【知~學(xué)習(xí)】誦讀 《六項(xiàng)精進(jìn)》1遍 累計(jì)5遍 《大學(xué)》1遍 累計(jì)5遍 ? 【經(jīng)典名句分享】 ...
    不要有感性的煩惱閱讀 88評(píng)論 0 0