1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        React18正式版源碼級(jí)剖析

        共 9341字,需瀏覽 19分鐘

         ·

        2022-04-04 06:28

        本文適合對(duì)React18.0.0源碼感興趣的小伙伴閱讀。

        歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~

        一、前言

        本文是廣東靚仔的好友bubucuo-高老師寫的,高老師最近在準(zhǔn)備React18的視頻,有興趣的小伙伴可以去學(xué)習(xí)學(xué)習(xí)。


        React18最重要的改變必須是Concurrent,就像哪吒降生一樣,打磨了很長時(shí)間了,終于正式見人了。


        a63be0e85f00f2cfc2dc54f51910b4b5.webp


        Concurrent Or Concurrency,中文我們通常翻譯為并發(fā),也有少部分翻譯成并行。React已經(jīng)著手開發(fā)Concurrent幾年了,但是一直只存在于實(shí)驗(yàn)版本。到了React18,Concurrent終于正式投入使用了。


        Concurrent并不是API之類的特性,而是一種能讓你的React項(xiàng)目同時(shí)具有多個(gè)版本UI的幕后機(jī)制,相當(dāng)愛迪生背后的特斯拉。


        Concurrent很重要,雖然它不是API之類的新特性,但是如果你想解鎖React18的大部分新特性,諸如transition、Suspense等,背后就要依賴Concurrent這位大佬。


        React雖然一直在強(qiáng)調(diào)開發(fā)者并不真的需要了解Concurrent是什么,但是忽然來了一句:

        49ab86599eaa739ae8b4584e9fbc29e6.webp


        是的,如果你不想追求high level,就別學(xué)了。

        二、Concurrent

        什么是Concurrent

        Concurrent最主要的特點(diǎn)就渲染是可中斷的。沒錯(cuò),以前是不可中斷的,也就是說,以前React中的update是同步渲染,在這種情況下,一旦update開啟,在任務(wù)完成前,都不可中斷。

        注意:這里說的同步,和setState所謂的同步異步不是一碼事,而且setState所謂的異步本質(zhì)上是個(gè)批量處理。

        Concurrent模式特點(diǎn)

        在Concurrent模式下,update開始了也可以中斷,晚點(diǎn)再繼續(xù)嘛,當(dāng)然中間也可能被遺棄掉。


        關(guān)于可中斷

        先說可中斷這件事情的重要性。對(duì)于React來說,任務(wù)可能很多,如果不區(qū)分優(yōu)先級(jí),那就是先來后到的順序。雖然聽起來很合理,但是現(xiàn)實(shí)是普通車輛就應(yīng)該給救護(hù)車讓路,因?yàn)槭掠休p重緩急嘛。那么在React中呢,如果高優(yōu)先級(jí)任務(wù)來了,但是低優(yōu)先級(jí)任務(wù)還沒有處理完畢,就會(huì)造成高優(yōu)先級(jí)任務(wù)等待的局面。比如說,某個(gè)低優(yōu)先級(jí)任務(wù)還在緩慢中,input框忽然被用戶觸發(fā),但是由于主線程被占著,沒有人搭理用戶,結(jié)果是用戶哐哐輸入,但是input沒有任何反應(yīng)。用戶一怒之下就走了,那你那個(gè)低優(yōu)先級(jí)的任務(wù)還更新個(gè)什么呢,用戶都沒了。


        由此可見,對(duì)于復(fù)雜項(xiàng)目來說,任務(wù)可中斷這件事情很重要。那么問題來了,React是如何做到的呢,其實(shí)基礎(chǔ)還是fiber,fiber本身鏈表結(jié)構(gòu),就是指針嘛,想指向別的地方加個(gè)屬性值就行了。


        關(guān)于被遺棄

        在Concurrent模式下,有些update可能會(huì)被遺棄掉。先舉個(gè)??:
        比如說,我看電視的時(shí)候,切換遙控器,從1頻道切換到2頻道,再切換到3頻道,最后在4頻道停下來。假如這些頻道都是UI,那么2、3頻道的渲染其實(shí)我并不關(guān)心,我只關(guān)心4頻道的結(jié)果,如果你非要花時(shí)間把2和3頻道的UI也渲染出來,最終導(dǎo)致4頻道很久之后才渲染出來,那我肯定不開心。正確的做法應(yīng)該是盡快渲染4頻道就行了,至于2和3頻道,不管渲染了多少了,遺棄了就行了,反正也不需要了。
        最后回到項(xiàng)目的實(shí)際場景,比如我想在淘寶搜索“老人與?!保敲次以谳斎肟蜉斎搿袄先伺c?!钡倪^程中,“老人”會(huì)有對(duì)應(yīng)的模糊查詢結(jié)果,但是不一定是我想要的結(jié)果,所以這個(gè)時(shí)候的模糊查詢框的update就是低優(yōu)先級(jí),“老人”對(duì)應(yīng)UI的update相對(duì)input的update,優(yōu)先級(jí)就會(huì)低一些。在現(xiàn)在React18中,這個(gè)模糊查詢相關(guān)的UI可以被當(dāng)做transition。關(guān)于transition,等下我會(huì)有細(xì)講。

        4c22589a86bd67107c96facb869b8004.webp


        關(guān)于狀態(tài)復(fù)用

        Concurrent模式下,還支持狀態(tài)的復(fù)用。某些情況下,比如用戶走了,又回來,那么上一次的頁面狀態(tài)應(yīng)當(dāng)被保存下來,而不是完全從頭再來。當(dāng)然實(shí)際情況下不能緩存所有的頁面,不然內(nèi)存不得爆炸,所以還得做成可選的。目前,React正在用Offscreen組件來實(shí)現(xiàn)這個(gè)功能。嗯,也就是這關(guān)于這個(gè)狀態(tài)復(fù)用,其實(shí)還沒完成呢。不過源碼中已經(jīng)在做了:

        64d475b8ba6f3672a0b7c6f9ecd1d088.webp

        另外,使用OffScreen,除了可以復(fù)用原先的狀態(tài),我們也可以使用它來當(dāng)做新UI的緩存準(zhǔn)備,就是雖然新UI還沒登場,但是可以先在后臺(tái)準(zhǔn)備著嘛,這樣一旦輪到它,就可以立馬快速地渲染出來。

        Concurrent總結(jié)

        總結(jié)一下,Concurrent并不是API之類的新特性,但是呢,它很重要,因?yàn)樗荝eact18大部分新特性的實(shí)現(xiàn)基礎(chǔ),包括Suspense、transitions、流式服務(wù)端渲染等。

        三、React的新特性

        前文說了那么多Concurrent并不是新特性,而是React18新特性的實(shí)現(xiàn)基礎(chǔ)。那么新特性都有哪些呢,下面來看吧:

        react-dom/client中的createRoot

        創(chuàng)建一個(gè)初次渲染或者更新,以前我們用的是ReactDOM.render,現(xiàn)在改用react-dom/client中的createRoot,這個(gè)函數(shù)的返回值是卸載函數(shù)。


        ssr中的ReactDOM.hydrate也換成了新的hydrateRoot。


        以上兩個(gè)API目前依然支持,只是已經(jīng)移入legacy模式,開發(fā)環(huán)境下會(huì)報(bào)warning。

        自動(dòng)批量處理 Automatic Batching

        如果你是React技術(shù)棧,那么你一定遇到過無數(shù)次這樣的面試題:

        6836b79bf46fba98256d56bdbb168dd3.webp


        恭喜你,接下來React18之后,這個(gè)面試題中的前半部分可以被劃入史冊(cè)了,但是后半部分依然是你我React技術(shù)黨逃不開的宿命。不過也不是什么大事,誰讓你認(rèn)識(shí)我呢~
        先回答上面那個(gè)問題,可同步可異步,同步的話把setState放在promises、setTimeout或者原生事件中等。所謂異步就是個(gè)批量處理,為什么要批量處理呢。舉個(gè)例子,老人以打漁為生,難道要每打到一條沙丁魚就下船去集市上賣掉嗎,那跑來跑去的成本太高了,賣魚的錢都不夠路費(fèi)的。所以老人都是打到魚之后先放到船艙,一段時(shí)間之后再跑一次集市,批量賣掉那些魚。對(duì)于React來說,也是這樣,state攢夠了再一起更新嘛。


        但是以前的React的批量更新是依賴于合成事件的,到了React18之后,state的批量更新不再與合成事件有直接關(guān)系,而是自動(dòng)批量處理。
        //?以前:?這里的兩次setState并沒有批量處理,React會(huì)render兩次
        setTimeout(()?=>?{
        ??setCount(c?=>?c?+?1);
        ??setFlag(f?=>?!f);
        },?1000);

        //?React18:?自動(dòng)批量處理,這里只會(huì)render一次
        setTimeout(()?=>?{
        ??setCount(c?=>?c?+?1);
        ??setFlag(f?=>?!f);
        },?1000);

        所以如果你項(xiàng)目中還在用setTimeout之列的“黑科技”實(shí)現(xiàn)setState的同步的話,升級(jí)React18之前,記得改一下~


        雖然建議setState批量處理,但是如果你有一些其它理由或者需要應(yīng)急,想要同步setState,這個(gè)時(shí)候可以使用flushSync,下面的例子中,log的count將會(huì)和button上的count同步:
        ???//?import?{?flushSync?}?from?"react-dom";
        ???changeCount?=?()?=>?{
        ????const?{?count?}?=?this.state;

        ????flushSync(()?=>?{
        ??????this.setState({
        ????????count:?count?+?1,
        ??????});
        ????});

        ????console.log("改變count",?this.state.count);?//sy-log
        ??};
        ??
        ??//?change?count?合成事件

        transition

        React把update分成兩種:

        • Urgent updates?緊急更新,指直接交互,通常指的用戶交互。如點(diǎn)擊、輸入等。這種更新一旦不及時(shí),用戶就會(huì)覺得哪里不對(duì)。

        • Transition updates?過渡更新,如UI從一個(gè)視圖向另一個(gè)視圖的更新。通常這種更新用戶并不著急看到。


        startTransition

        startTransition可以用在任何你想更新的時(shí)候。但是從實(shí)際來說,以下是兩種典型適用場景:

        • 渲染慢:如果你有很多沒那么著急的內(nèi)容要渲染更新。

        • 網(wǎng)絡(luò)慢:如果你的更新需要花較多時(shí)間從服務(wù)端獲取。這個(gè)時(shí)候也可以再結(jié)合Suspense

        import?{useEffect,?useState,?Suspense}?from?"react";
        import?Button?from?"../components/Button";
        import?User?from?"../components/User";
        import?Num?from?"../components/Num";
        import?{fetchData}?from?"../utils";

        const?initialResource?=?fetchData();

        export?default?function?TransitionPage(props)?{
        ??const?[resource,?setResource]?=?useState(initialResource);

        ??//?useEffect(()?=>?{
        ??//???console.log("resource",?resource);?//sy-log
        ??//?},?[resource]);

        ??return?(
        ????<div>
        ??????<h3>TransitionPageh3>

        ??????<Suspense?fallback={<h1>loading?-?userh1>}>
        ????????<User?resource={resource}?/>
        ??????Suspense>

        ??????<Suspense?fallback={<h1>loading-numh1>}>
        ????????<Num?resource={resource}?/>
        ??????Suspense>

        ??????<Button
        ????????refresh={()?=>
        ?{
        ??????????setResource(fetchData());
        ????????}}
        ??????/>
        ????div>
        ??);
        }


        Button

        import?{
        ??//startTransition,
        ??useTransition,
        }?from?"react";

        export?default?function?Button({refresh})?{
        ??const?[isPending,?startTransition]?=?useTransition();

        ??return?(
        ????<div?className="border">
        ??????<h3>Buttonh3>

        ??????<button
        ????????onClick={()?=>
        ?{
        ??????????startTransition(()?=>?{
        ????????????refresh();
        ??????????});
        ????????}}
        ????????disabled={isPending}>
        ????????點(diǎn)擊刷新數(shù)據(jù)
        ??????button>
        ??????{isPending???<div>loading...div>?:?null}
        ????div>
        ??);
        }

        與setTimeout異同

        startTransition出現(xiàn)之前,我們可以使用setTimeout來實(shí)現(xiàn)優(yōu)化。但是現(xiàn)在在處理上面的優(yōu)化的時(shí)候,有了startTransition基本上可以拋棄setTimeout了,原因主要有以三點(diǎn):首先,與setTimeout不同的是,startTransition并不會(huì)延遲調(diào)度,而是會(huì)立即執(zhí)行,startTransition接收的函數(shù)是同步執(zhí)行的,只是這個(gè)update被加了一個(gè)“transitions"的標(biāo)記。而這個(gè)標(biāo)記,React內(nèi)部處理更新的時(shí)候是會(huì)作為參考信息的。這就意味著,相比于setTimeout, 把一個(gè)update交給startTransition能夠更早地被處理。而在于較快的設(shè)備上,這個(gè)過度是用戶感知不到的。

        useTransition

        在使用startTransition更新狀態(tài)的時(shí)候,用戶可能想要知道transition的實(shí)時(shí)情況,這個(gè)時(shí)候可以使用React提供的hook api?useTransition

        import?{?useTransition?}?from?'react';
        const?[isPending,?startTransition]?=?useTransition();

        如果transition未完成,isPending值為true,否則為false。


        useDeferredValue

        使得我們可以延遲更新某個(gè)不那么重要的部分。

        相當(dāng)于參數(shù)版的transitions。

        舉例:如下圖,當(dāng)用戶在輸入框輸入“書”的時(shí)候,用戶應(yīng)該立馬看到輸入框的反應(yīng),而相比之下,下面的模糊查詢框如果延遲出現(xiàn)一會(huì)兒其實(shí)是完全可以接受的,因?yàn)橛脩艨赡軙?huì)繼續(xù)修改輸入框內(nèi)容,這個(gè)過程中模糊查詢結(jié)果還是會(huì)變化,但是這個(gè)變化對(duì)用戶來說相對(duì)沒那么重要,用戶最關(guān)心的是看到最后的匹配結(jié)果。


        82efe340cc847195cd0c4136a635bf8a.webp

        用法如下:

        import?{useDeferredValue,?useState}?from?"react";
        import?MySlowList?from?"../components/MySlowList";

        export?default?function?UseDeferredValuePage(props)?{
        ??const?[text,?setText]?=?useState("hello");
        ??const?deferredText?=?useDeferredValue(text);

        ??const?handleChange?=?(e)?=>?{
        ????setText(e.target.value);
        ??};
        ??return?(
        ????<div>
        ??????<h3>UseDeferredValuePageh3>

        ??????{/*?保持將當(dāng)前文本傳遞給?input?*/}
        ??????<input?value={text}?onChange={handleChange}?/>
        ??????{/*?但在必要時(shí)可以將列表“延后”?*/}
        ??????<p>{deferredText}p>

        ??????<MySlowList?text={deferredText}?/>
        ????div>
        ??);
        }

        MySlowList

        import?React,?{memo}?from?"react";

        function?ListItem({children})?{
        ??let?now?=?performance.now();
        ??while?(performance.now()?-?now?3)?{}
        ??return?<div?className="ListItem">{children}div>;
        }

        export?default?memo(function?MySlowList({text})?{
        ??let?items?=?[];
        ??for?(let?i?=?0;?i?80;?i++)?{
        ????items.push(
        ??????<ListItem?key={i}>
        ????????Result?#{i}?for?"{text}"
        ??????ListItem>

        ????);
        ??}
        ??return?(
        ????<div?className="border">
        ??????<p>
        ????????<b>Results?for?"{text}":b>

        ??????p>
        ??????<ul?className="List">{items}ul>
        ????div>
        ??);
        });

        Suspense

        可以“等待”目標(biāo)UI加載,并且可以直接指定一個(gè)加載的界面(像是個(gè) spinner),讓它在用戶等待的時(shí)候顯示。
        }>
        ??<Comments?/>
        </Suspense>
        其實(shí)Suspense也早就出現(xiàn)在React中了,只不過之前功能有限。在React18中,背靠Concurrent模式,Suspense終于爆發(fā)了自己的光彩。


        在概念上,Suspense有點(diǎn)像catch,只不過Suspense捕獲的不是異常,而是組件的suspending狀態(tài),即掛載中。


        基本使用:避免等待太久

        import?{useState,?Suspense}?from?"react";
        import?User?from?"../components/User";
        import?Num?from?"../components/Num";
        import?{fetchData}?from?"../utils";
        import?ErrorBoundaryPage?from?"./ErrorBoundaryPage";

        const?initialResource?=?fetchData();

        export?default?function?SuspensePage(props)?{
        ??const?[resource,?setResource]?=?useState(initialResource);

        ??return?(
        ????<div>
        ??????<h3>SuspensePageh3>

        ??????<ErrorBoundaryPage?fallback={<h1>網(wǎng)絡(luò)出錯(cuò)了h1>}>
        ????????<Suspense?fallback={<h1>loading?-?userh1>}>
        ??????????<User?resource={resource}?/>
        ????????Suspense>
        ??????ErrorBoundaryPage>

        ??????<Suspense?fallback={<h1>loading-numh1>}>
        ????????<Num?resource={resource}?/>
        ??????Suspense>

        ??????<button?onClick={()?=>?setResource(fetchData())}>refreshbutton>
        ????div>
        ??);
        }

        錯(cuò)誤處理

        每當(dāng)使用 Promises,大概率我們會(huì)用?catch()?來做錯(cuò)誤處理。但當(dāng)我們用 Suspense 時(shí),我們不等待?Promises 就直接開始渲染,這時(shí)?catch()?就不適用了。這種情況下,錯(cuò)誤處理該怎么進(jìn)行呢?在 Suspense 中,獲取數(shù)據(jù)時(shí)拋出的錯(cuò)誤和組件渲染時(shí)的報(bào)錯(cuò)處理方式一樣——你可以在需要的層級(jí)渲染一個(gè)錯(cuò)誤邊界組件來“捕捉”層級(jí)下面的所有的報(bào)錯(cuò)信息。
        export?default?class?ErrorBoundaryPage?extends?React.Component?{
        ??state?=?{hasError:?false,?error:?null};
        ??static?getDerivedStateFromError(error)?{
        ????return?{
        ??????hasError:?true,
        ??????error,
        ????};
        ??}
        ??render()?{
        ????if?(this.state.hasError)?{
        ??????return?this.props.fallback;
        ????}
        ????return?this.props.children;
        ??}
        }


        結(jié)合transitions

        所謂提高用戶體驗(yàn),一個(gè)重要的準(zhǔn)則就是保證UI的連續(xù)性,如下面的例子,如果此時(shí)我想把tab從‘photos’切換到‘comments’,但是Comments又沒法立馬渲染出來,這個(gè)時(shí)候不可避免地,就會(huì)Photos頁面消失,顯現(xiàn)Spinner的loading頁面,等一會(huì)兒,Comments頁面才姍姍來遲。
        function?handleClick()?{
        ??setTab('comments');
        }

        }>
        ??{tab?===?'photos'???<Photos?/>?:?<Comments?/>}
        </Suspense>
        從UI連續(xù)性上來說,這個(gè)中間出現(xiàn)的Spinner就已經(jīng)破壞了連續(xù)性。而實(shí)際上,正常人的反應(yīng)其實(shí)是沒有那么快,短暫的延遲我們是感覺不到的。所以考慮到UI的連續(xù)性,上面的例子,交互可不可以修改一下,把上面頁面的切換當(dāng)做transitions,這樣即使tab切換,但是依然短暫停留在Photos,之后再改變到

        Comments:

        function?handleClick()?{
        ??startTransition(()?=>?{
        ????setTab('comments');
        ??});
        }


        上面這個(gè)例子我們使用的是startTransition,如果需要知道pending狀態(tài),可以使用useTransition:
        const?[isPending,?startTransition]?=?useTransition();

        function?handleClick()?{
        ??startTransition(()?=>?{
        ????setTab('comments');
        ??});
        }

        }>
        ??<div?style={{?opacity:?isPending???0.8?:?1?}}>
        ????{tab?===?'photos'???<Photos?/>?:?<Comments?/>}
        ??div>

        </Suspense>

        SuspenseList

        用于控制Suspense組件的顯示順序。

        revealOrder?Suspense加載順序

        together?所有Suspense一起顯示,也就是最后一個(gè)加載完了才一起顯示全部

        forwards?按照順序顯示Suspense

        backwards?反序顯示Suspense

        tail是否顯示fallback,只在revealOrder為forwards或者backwards時(shí)候有效

        hidden不顯示

        collapsed輪到自己再顯示

        import?{useState,?Suspense,?SuspenseList}?from?"react";
        import?User?from?"../components/User";
        import?Num?from?"../components/Num";
        import?{fetchData}?from?"../utils";
        import?ErrorBoundaryPage?from?"./ErrorBoundaryPage";

        const?initialResource?=?fetchData();

        export?default?function?SuspenseListPage(props)?{
        ??const?[resource,?setResource]?=?useState(initialResource);

        ??return?(
        ????<div>
        ??????<h3>SuspenseListPageh3>

        ??????<SuspenseList?tail="collapsed">
        ????????<ErrorBoundaryPage?fallback={<h1>網(wǎng)絡(luò)出錯(cuò)了h1>}>
        ??????????<Suspense?fallback={<h1>loading?-?userh1>}>
        ????????????<User?resource={resource}?/>
        ??????????Suspense>
        ????????ErrorBoundaryPage>

        ????????<Suspense?fallback={<h1>loading-numh1>}>
        ??????????<Num?resource={resource}?/>
        ????????Suspense>
        ??????SuspenseList>

        ??????<button?onClick={()?=>?setResource(fetchData())}>refreshbutton>
        ????div>
        ??);
        }

        四、新的Hooks

        8105cf7a084bd04f9cd5d3cb8066c7fd.webp


        關(guān)于useTransition與useDeferredValue上面已經(jīng)介紹過了,接下來說下React18其它的新Hooks,其中useSyncExternalStore與useInsertionEffect屬于Library Hooks。也就是普通應(yīng)用開發(fā)者一般用不到,這倆主要用于那些需要深度融合React模型的庫開發(fā),比如Recoil等。

        useId

        用于產(chǎn)生一個(gè)在服務(wù)端與Web端都穩(wěn)定且唯一的ID,也支持加前綴,這個(gè)特性多用于支持ssr的環(huán)境下:

        export?default?function?NewHookApi(props)?{
        ??const?id?=?useId();

        ??return?(
        ????<div>
        ??????<h3?id={id}>NewHookApih3>

        ????div>
        ??);
        }

        注意:useId產(chǎn)生的ID不支持css選擇器,如querySelectorAll。

        useSyncExternalStore

        const?state?=?useSyncExternalStore(subscribe,?getSnapshot[,?getServerSnapshot]);

        此Hook用于外部數(shù)據(jù)的讀取與訂閱,可應(yīng)用Concurrent。

        基本用法如下:

        import?{?useStore?}?from?"../store";
        import?{?useId,?useSyncExternalStore?}?from?"../whichReact";

        export?default?function?NewHookApi(props)?{
        ??const?store?=?useStore();
        ??const?state?=?useSyncExternalStore(store.subscribe,?store.getSnapshot);

        ??return?(
        ????<div>
        ??????<h3>NewHookApih3>


        ??????<button?onClick={()?=>?store.dispatch({?type:?"ADD"?})}>{state}button>
        ????div>
        ??);
        }

        useStore是我另外定義的,

        export?function?useStore()?{
        ??const?storeRef?=?useRef();

        ??if?(!storeRef.current)?{
        ????storeRef.current?=?createStore(countReducer);
        ??}

        ??return?storeRef.current;
        }

        function?countReducer(action,?state?=?0)?{
        ??switch?(action.type)?{
        ????case?"ADD":
        ??????return?state?+?1;
        ????case?"MINUS":
        ??????return?state?-?1;
        ????default:
        ??????return?state;
        ??}
        }

        這里的createStore用的redux思路:

        export?function?createStore(reducer)?{
        ??let?currentState;
        ??let?listeners?=?[];

        ??function?getSnapshot()?{
        ????return?currentState;
        ??}

        ??function?dispatch(action)?{
        ????currentState?=?reducer(action,?currentState);
        ????listeners.map((listener)?=>?listener());
        ??}

        ??function?subscribe(listener)?{
        ????listeners.push(listener);

        ????return?()?=>?{
        ??????//???console.log("unmount",?listeners);
        ????};
        ??}

        ??dispatch({?type:?"TIANNA"?});

        ??return?{
        ????getSnapshot,
        ????dispatch,
        ????subscribe,
        ??};
        }

        對(duì)于還在用自定義store來做低代碼項(xiàng)目的我有點(diǎn)開心,可以用于升級(jí)我的項(xiàng)目了,原先定義的forceUpdate、unsubscribe之類的,可以去掉了~

        useInsertionEffect

        useInsertionEffect(didUpdate);

        函數(shù)簽名同useEffect,但是它是在所有DOM變更前同步觸發(fā)。主要用于css-in-js庫,往DOM中動(dòng)態(tài)注入

        <table id="7actg"></table>

        <address id="7actg"></address>
        <address id="7actg"></address>
        1. <object id="7actg"><tt id="7actg"></tt></object>
          一本大道东京热无码 | 五月天久久性爱 | 在线免费看污片 | 人人超碰日摸在线观看熟女 | 中国护士性满足hd | 国产精品国产三级国产传播 | 老司机av网| 在线看一级成人小电影 | 色秘 乱码一区二区三区男奴 | 怡春院综合 |