<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 全覽

          共 10310字,需瀏覽 21分鐘

           ·

          2022-04-19 12:07

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

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

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

          Concurrent Mode

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

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

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

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

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

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

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

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

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

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

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

          startTransition

          我們?nèi)绻鲃?dòng)發(fā)揮 CM 的優(yōu)勢(shì),那就離不開 startTransition。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          那我們的代碼會(huì)很簡(jiǎn)單,如下所示,我們只需要一個(gè) 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 拖動(dòng)后,React 執(zhí)行流程大致如下:

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

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

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



          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)先級(jí)的,可以被隨時(shí)中止,保證了高優(yōu)先級(jí)的 Slider 的體驗(yàn)。

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

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

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

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

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

          //?實(shí)時(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>

          ??
          )

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

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

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

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

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

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

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

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

          如果你在某種場(chǎng)景下不想使用批處理,你可以通過 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 的批處理在絕大部分場(chǎng)景下是沒有影響,但在 Class 組件中,如果你在兩次 setState 中間讀取了 state 值,會(huì)出現(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í)行的,如果其中有一步比較慢,都會(huì)影響整體的渲染速度。

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

          假設(shè)我們有一個(gè)頁面,包含了 NavBar、Sidebar、Post、Comments 等幾個(gè)部分,在傳統(tǒng)的 SSR 模式下,我們必須請(qǐng)求到 Post 數(shù)據(jù),請(qǐng)求到 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ù)請(qǐng)求很慢,會(huì)拖慢整個(gè)流程。

          在 React 18 中,我們通過 Suspense包裹,可以告訴 React,我們不需要等這個(gè)組件,可以先返回其它內(nèi)容,等這個(gè)組件準(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 會(huì)通過同一個(gè)流(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>
                    超逼视频在线观看 | 久久国产精品影院 | 99久热精品视频 | www.欧美黄 | 欧美性爱网站在线 |