<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 18 全覽

          共 10300字,需瀏覽 21分鐘

           ·

          2022-04-23 17:32

          在 2021 年 6 月 8 號,React 公布了 v18 版本的發(fā)布計劃,并發(fā)布了 alpha 版本。經(jīng)過將近一年的發(fā)布前準(zhǔn)備,在 2022 年 3 月 29 日,React 18 正式版終于和大家見面了。

          React 18 應(yīng)該是最近幾年的一個重磅版本,React 官方對它寄予了厚望。不然也不會將 React 17 作為一個過渡版本,也不會光發(fā)布準(zhǔn)備工作就做了一年。

          在過去一年,我們已經(jīng)或多或少了解到一些 React 18 的新功能。這篇文章我會通過豐富的示例,向大家系統(tǒng)的介紹 React 18 帶來的改變。當(dāng)然本文融入了很多個人理解,如有不對,煩請指正。

          Concurrent Mode

          Concurrent Mode(以下簡稱 CM)翻譯叫并發(fā)模式,這個概念我已經(jīng)聽了好多年了,并且一度非常擔(dān)憂

          • React 官方憋了好多年的大招,會不會是一個破壞性不兼容的超級大版本?就像 VUE v3 和 v2。
          • 現(xiàn)有的生態(tài)是不是都得跟著大版本升級?比如 ant design,ahooks 等。

          隨著對 CM 的了解,我發(fā)現(xiàn)它其實(shí)是人畜無害的。

          CM 本身并不是一個功能,而是一個底層設(shè)計,它使 React 能夠同時準(zhǔn)備多個版本的 UI。

          在以前,React 在狀態(tài)變更后,會開始準(zhǔn)備虛擬 DOM,然后渲染真實(shí) DOM,整個流程是串行的。一旦開始觸發(fā)更新,只能等流程完全結(jié)束,期間是無法中斷的。

          在 CM 模式下,React 在執(zhí)行過程中,每執(zhí)行一個 Fiber,都會看看有沒有更高優(yōu)先級的更新,如果有,則當(dāng)前低優(yōu)先級的的更新會被暫停,待高優(yōu)先級任務(wù)執(zhí)行完之后,再繼續(xù)執(zhí)行或重新執(zhí)行。

          CM 模式有點(diǎn)類似計算機(jī)的多任務(wù)處理,處理器在同時進(jìn)行的應(yīng)用程序之間快速切換,也許 React 應(yīng)該改名叫 ReactOS 了。

          這里舉個例子:我們正在看電影,這時候門鈴響了,我們要去開門拿快遞。在 React 18 以前,一旦我們開始看電影,就不能被終止,必須等電影看完之后,才會去開門。而在 React 18 CM 模式之后,我們就可以暫停電影,等開門拿完快遞之后,再重新繼續(xù)看電影。

          不過對于普通開發(fā)者來說,我們一般是不會感知到 CM 的存在的,在升級到 React 18 之后,我們的項(xiàng)目不會有任何變化

          我們需要關(guān)注的是基于 CM 實(shí)現(xiàn)的上層功能,比如 Suspense、Transitions、streaming server rendering(流式服務(wù)端渲染), 等等。

          React 18 的大部分功能都是基于 CM 架構(gòu)實(shí)現(xiàn)出來的,并且這這是一個開始,未來會有更多基于 CM 實(shí)現(xiàn)的高級能力。

          startTransition

          我們?nèi)绻鲃影l(fā)揮 CM 的優(yōu)勢,那就離不開 startTransition。

          React 的狀態(tài)更新可以分為兩類:

          • 緊急更新(Urgent updates):比如打字、點(diǎn)擊、拖動等,需要立即響應(yīng)的行為,如果不立即響應(yīng)會給人很卡,或者出問題了的感覺
          • 過渡更新(Transition updates):將 UI 從一個視圖過渡到另一個視圖。不需要即時響應(yīng),有些延遲是可以接受的。

          我以前會認(rèn)為,CM 模式會自動幫我們區(qū)分不同優(yōu)先級的更新,一鍵無憂享受。很遺憾的是,CM 只是提供了可中斷的能力,默認(rèn)情況下,所有的更新都是緊急更新。

          這是因?yàn)?React 并不能自動識別哪些更新是優(yōu)先級更高的。

          const?[inputValue,?setInputValue]?=?useState();

          const?onChange?=?(e)=>{
          ??setInputValue(e.target.value);
          ??//?更新搜索列表
          ??setSearchQuery(e.target.value);
          }

          return?(
          ??<input?value={inputValue}?onChange={onChange}?/>
          )

          比如以上示例,用戶的鍵盤輸入操作后,setInputValue會立即更新用戶的輸入到界面上,是緊急更新。而setSearchQuery是根據(jù)用戶輸入,查詢相應(yīng)的內(nèi)容,是非緊急的。

          但是 React 確實(shí)沒有能力自動識別。所以它提供了 startTransition讓我們手動指定哪些更新是緊急的,哪些是非緊急的。

          //?緊急的
          setInputValue(e.target.value);
          startTransition(()?=>?{
          ??setSearchQuery(input);?//?非緊急的
          });

          如上代碼,我們通過 startTransition來標(biāo)記一個非緊急更新,讓該狀態(tài)觸發(fā)的變更變成低優(yōu)先級的。

          光用文字描述大家可能沒有體驗(yàn),接下來我們通過一個示例來認(rèn)識下可中斷渲染對性能的爆炸提升。

          示例頁面:https://react-fractals-git-react-18-swizec.vercel.app/[1]

          如下圖,我們需要畫一個畢達(dá)哥拉斯樹,通過一個 Slider 來控制樹的傾斜。

          那我們的代碼會很簡單,如下所示,我們只需要一個 treeLeanstate 來管理狀態(tài)。

          const?[treeLean,?setTreeLean]?=?useState(0)

          function?changeTreeLean(event)?{
          ??const?value?=?Number(event.target.value);
          ??setTreeLean(value);
          }

          return?(
          ??<>
          ????<input?type="range"?value={treeLean}?onChange={changeTreeLean}?/>
          ????<Pythagoras?lean={treeLean}?/>
          ??

          )

          在每次 Slider 拖動后,React 執(zhí)行流程大致如下:

          1. 更新 treeLean
          2. 渲染 input,填充新的 value
          3. 重新渲染樹組件 Pythagoras

          每一次用戶拖動 Slider,都會同步執(zhí)行上述三步。但當(dāng)樹的節(jié)點(diǎn)足夠多的時候,Pythagoras 渲染一次就非常慢,就會導(dǎo)致 Slider 的 value 回填變慢,用戶感覺到嚴(yán)重的卡頓。如下圖。

          當(dāng)數(shù)的節(jié)點(diǎn)足夠大時,已經(jīng)卡到爆炸了。在 React 18 以前,我們是沒有什么好的辦法來解決這個問題的。但基于 React 18 CM 的可中斷渲染機(jī)制,我們可以將樹的更新渲染標(biāo)記為低優(yōu)先級的,就不會感覺到卡頓了。



          const?[treeLeanInput,?setTreeLeanInput]?=?useState(0);
          const?[treeLean,?setTreeLean]?=?useState(0);

          function?changeTreeLean(event)?{
          ??const?value?=?Number(event.target.value);
          ??setTreeLeanInput(value)

          ??//?將?treeLean?的更新用?startTransition?包裹
          ??React.startTransition(()?=>?{
          ????setTreeLean(value);
          ??});
          }

          return?(
          ??<>
          ????<input?type="range"?value={treeLeanInput}?onChange={changeTreeLean}?/>
          ????<Pythagoras?lean={treeLean}?/>
          ??

          )

          以上代碼,我們通過 startTransition 標(biāo)記了非緊急更新,讓樹的更新變成低優(yōu)先級的,可以被隨時中止,保證了高優(yōu)先級的 Slider 的體驗(yàn)。

          此時更新流程變?yōu)榱?/p>

          1. input 更新
            1. treeLeanInput 狀態(tài)變更
            2. 準(zhǔn)備新的 DOM
            3. 渲染 DOM
          2. 樹更新(這一次更新是低優(yōu)先級的,隨時可以被中止)
            1. treeLean 狀態(tài)變更
            2. 準(zhǔn)備新的 DOM
            3. 渲染 DOM

          React 會在高優(yōu)先級更新渲染完成之后,才會啟動低優(yōu)先級更新渲染,并且低優(yōu)先級渲染隨時可被其它高優(yōu)先級更新中斷。

          當(dāng)然,在低優(yōu)先狀態(tài)等待更新過程中,如果能有一個 Loading 狀態(tài),那就更好了。React 18 提供了 useTransition來跟蹤 transition 狀態(tài)。

          const?[treeLeanInput,?setTreeLeanInput]?=?useState(0);
          const?[treeLean,?setTreeLean]?=?useState(0);

          //?實(shí)時監(jiān)聽?transition?狀態(tài)
          const?[isPending,?startTransition]?=?useTransition();

          function?changeTreeLean(event)?{
          ??const?value?=?Number(event.target.value);
          ??setTreeLeanInput(value)

          ??React.startTransition(()?=>?{
          ????setTreeLean(value);
          ??});
          }

          return?(
          ??<>
          ????<input?type="range"?value={treeLeanInput}?onChange={changeTreeLean}?/>
          ????<Spin?spinning={isPending}>
          ??????<Pythagoras?lean={treeLean}?/>
          ????Spin>

          ??
          )

          自動批處理 Automatic Batching

          批處理是指 React 將多個狀態(tài)更新,聚合到一次 render 中執(zhí)行,以提升性能。比如

          function?handleClick()?{
          ??setCount(c?=>?c?+?1);
          ??setFlag(f?=>?!f);
          ??//?React?只會?re-render?一次,這就是批處理
          }

          在 React 18 之前,React 只會在事件回調(diào)中使用批處理,而在 Promise、setTimeout、原生事件等場景下,是不能使用批處理的。

          setTimeout(()?=>?{
          ??setCount(c?=>?c?+?1);
          ??setFlag(f?=>?!f);
          ??//?React?會?render?兩次,每次?state?變化更新一次
          },?1000);

          而在 React 18 中,所有的狀態(tài)更新,都會自動使用批處理,不關(guān)心場景。

          function?handleClick()?{
          ??setCount(c?=>?c?+?1);
          ??setFlag(f?=>?!f);
          ??//?React?只會?re-render?一次,這就是批處理
          }

          setTimeout(()?=>?{
          ??setCount(c?=>?c?+?1);
          ??setFlag(f?=>?!f);
          ??//?React?只會?re-render?一次,這就是批處理
          },?1000);

          如果你在某種場景下不想使用批處理,你可以通過 flushSync來強(qiáng)制同步執(zhí)行(比如:你需要在狀態(tài)更新后,立刻讀取新 DOM 上的數(shù)據(jù)等。)

          import?{?flushSync?}?from?'react-dom';

          function?handleClick()?{
          ??flushSync(()?=>?{
          ????setCounter(c?=>?c?+?1);
          ??});
          ??//?React?更新一次?DOM
          ??flushSync(()?=>?{
          ????setFlag(f?=>?!f);
          ??});
          ??//?React?更新一次?DOM
          }

          React 18 的批處理在絕大部分場景下是沒有影響,但在 Class 組件中,如果你在兩次 setState 中間讀取了 state 值,會出現(xiàn)不兼容的情況,如下示例。

          handleClick?=?()?=>?{
          ??setTimeout(()?=>?{
          ????this.setState(({?count?})?=>?({?count:?count?+?1?}));

          ????//?在?React17?及之前,打印出來是?{?count:?1,?flag:?false?}
          ????//?在?React18,打印出來是?{?count:?0,?flag:?false?}
          ????console.log(this.state);

          ????this.setState(({?flag?})?=>?({?flag:?!flag?}));
          ??});
          };

          當(dāng)然你可以通過 flushSync來修正它。

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

          ????//?在?React18,打印出來是?{?count:?1,?flag:?false?}
          ????console.log(this.state);

          ????this.setState(({?flag?})?=>?({?flag:?!flag?}));
          ??});
          };

          流式 SSR

          SSR 一次頁面渲染的流程大概為:

          1. 服務(wù)器 fetch 頁面所需數(shù)據(jù)
          2. 數(shù)據(jù)準(zhǔn)備好之后,將組件渲染成 string 形式作為 response 返回
          3. 客戶端加載資源
          4. 客戶端合成(hydrate)最終的頁面內(nèi)容

          在傳統(tǒng)的 SSR 模式中,上述流程是串行執(zhí)行的,如果其中有一步比較慢,都會影響整體的渲染速度。

          而在 React 18 中,基于全新的 Suspense,支持了流式 SSR,也就是允許服務(wù)端一點(diǎn)一點(diǎn)的返回頁面。

          假設(shè)我們有一個頁面,包含了 NavBar、Sidebar、Post、Comments 等幾個部分,在傳統(tǒng)的 SSR 模式下,我們必須請求到 Post 數(shù)據(jù),請求到 Comments 數(shù)據(jù)后,才能返回完整的 HTML。


          ??<nav>
          ????
          ????<a?href="/">Homea>

          ???nav>
          ??<aside>
          ????
          ????<a?href="/profile">Profilea>

          ??aside>
          ??<article>
          ????
          ????<p>Hello?worldp>

          ??article>
          ??<section>
          ????
          ????<p>First?commentp>

          ????<p>Second?commentp>
          ??section>
          </main>

          但如果 Comments 數(shù)據(jù)請求很慢,會拖慢整個流程。

          在 React 18 中,我們通過 Suspense包裹,可以告訴 React,我們不需要等這個組件,可以先返回其它內(nèi)容,等這個組件準(zhǔn)備好之后,單獨(dú)返回。


          ??<NavBar?/>
          ??<Sidebar?/>
          ??<RightPane>
          ????<Post?/>
          ????<Suspense?fallback={<Spinner?/>}>
          ??????<Comments?/>
          ????Suspense>

          ??RightPane>
          </Layout>

          如上,我們通過 Suspense包裹了 Comments 組件,那服務(wù)器首次返回的 HTML 是下面這樣的,組件處通過 loading進(jìn)行了占位。


          ??<nav>
          ????
          ????<a?href="/">Homea>

          ???nav>
          ??<aside>
          ????
          ????<a?href="/profile">Profilea>

          ??aside>
          ??<article>
          ????
          ????<p>Hello?worldp>

          ??article>
          ??<section?id="comments-spinner">
          ????
          ????<img?width=400?src="spinner.gif"?alt="Loading..."?/>
          ??section>

          </main>

          當(dāng) 組件準(zhǔn)備好之后,React 會通過同一個流(stream)發(fā)送給瀏覽器(res.send 替換成 res.socket),并替換到相應(yīng)位置。

          "comments">
          ??
          ??<p>First?commentp>
          ??<p>Second?commentp>
          </div>
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    欧美特黄AA片 | 一区二区国产黄片视频在线 | 精品人妻午夜一区二区三区四区 | 91射射射 | 日本操逼小视频 |