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

          React18,不遠(yuǎn)啦?

          共 5504字,需瀏覽 12分鐘

           ·

          2021-06-03 12:08

          React前不久的一次PR #21488中,核心成員「Brian Vaughn」對(duì)React內(nèi)一些API、以及內(nèi)部flag作出調(diào)整。

          其中最引人注目的改動(dòng)是:React入口增加createRoot API。

          業(yè)界將這一變化解讀為:Concurrent Mode(后文簡(jiǎn)稱為CM)將在不久后穩(wěn)定,并出現(xiàn)在正式版中。

          React17是一個(gè)過渡版本,用以穩(wěn)定CM。一旦CM穩(wěn)定,那v18的進(jìn)度會(huì)大大加快。

          可以說從18年到21年,React團(tuán)隊(duì)的主要工作就是圍繞CM展開的,那么:

          • CM是什么?

          • CM能解決React什么問題?

          • 為什么經(jīng)歷快4年,跨越16、17兩個(gè)版本,CM還不穩(wěn)定?

          本文將作出解答。

          CM是什么

          要了解CM(并發(fā)模式)是什么,首先需要知道React源碼的運(yùn)行流程。

          React大體可以分為兩個(gè)工作階段:

          • render階段

          render階段會(huì)計(jì)算一次更新中變化的部分(通過diff算法),因組件的render函數(shù)在該階段調(diào)用而得名。

          render階段「可能」是異步的(取決于觸發(fā)更新的場(chǎng)景)。

          • commit階段

          commit階段會(huì)將render階段計(jì)算的需要變化的部分渲染在視圖中。對(duì)應(yīng)ReactDOM來說會(huì)執(zhí)行appendChild、removeChild等。

          commit階段一定是同步調(diào)用(這樣用戶不會(huì)看到渲染不完全的UI

          我們通過ReactDOM.render創(chuàng)建的應(yīng)用屬于legacy模式。

          在該模式下一次render階段對(duì)應(yīng)一次commit階段。

          如果我們通過ReactDOM.createRoot(當(dāng)前穩(wěn)定版本中還沒有此API)創(chuàng)建的應(yīng)用屬于開篇提到的CMconcurrent模式)

          CM下,更新有了優(yōu)先級(jí)的概念,render階段可能被高優(yōu)先級(jí)的更新打斷。

          所以render階段可能會(huì)重復(fù)多次(被打斷后重新開始)。

          可能多次render階段對(duì)應(yīng)一次commit階段。

          此外,還有個(gè)blocking模式用于方便開發(fā)者慢慢從legacy模式過渡到CM。

          你可以從特性對(duì)比看到不同模式支持的特性:

          不同模式支持的特性

          為什么需要CM?

          知道了CM是什么,那么他有什么用?為什么React核心團(tuán)隊(duì)會(huì)耗時(shí)3年多(18年開始)來實(shí)現(xiàn)他?

          這得從React的設(shè)計(jì)理念聊起。

          我們可以從官網(wǎng)React哲學(xué)看到React的設(shè)計(jì)理念:

          我們認(rèn)為,React是用JavaScript構(gòu)建「快速響應(yīng)」的大型Web應(yīng)用程序的首選方式。

          其中「快速響應(yīng)」是重點(diǎn)。

          那么什么影響「快速響應(yīng)」呢?React團(tuán)隊(duì)給出的答案:

          CPU的瓶頸和IO的瓶頸

          CPU的瓶頸

          考慮如下demo,我們渲染3000的列表項(xiàng):

          function App({
            const len = 3000;
            return (
              <ul>
                {Array(len).fill(0).map((_, i) => <li>{i}</li>)}
              </ul>

            );
          }

          const rootEl = document.querySelector("#root");
          ReactDOM.render(<App/>, rootEl);  

          剛才說過,在legacy模式下render階段不會(huì)被打斷,則這3000個(gè)lirender都得在同一個(gè)瀏覽器宏任務(wù)中完成。

          長時(shí)間的計(jì)算會(huì)阻塞線程,造成頁面掉幀,這就是CPU的瓶頸。

          解決的辦法就是:?jiǎn)⒂?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">CM,將render階段變?yōu)?strong style="color: rgb(145, 109, 213);">「可中斷」的,

          當(dāng)瀏覽器一幀剩余時(shí)間不多時(shí)將控制權(quán)交給瀏覽器。等下一幀的空余時(shí)間再繼續(xù)組件render

          IO的瓶頸

          除了長時(shí)間計(jì)算導(dǎo)致的卡頓,網(wǎng)絡(luò)請(qǐng)求時(shí)的loading狀態(tài)也會(huì)造成頁面不可交互,這就是IO的瓶頸。

          IO瓶頸是客觀存在的。

          作為前端,能做的只能是盡早請(qǐng)求需要的數(shù)據(jù)。

          但是,通常情況下:「代碼可維護(hù)性」「請(qǐng)求效率」是相悖的。

          什么意思呢,舉個(gè)例子:

          假設(shè)我們封裝了請(qǐng)求數(shù)據(jù)的方法useFetch,通過返回值是否存在區(qū)分是否請(qǐng)求到數(shù)據(jù)。

          function App({
            const data = useFetch();
            
            return {data ? <User data={data}/> : null};
          }

          為了提高「代碼可維護(hù)性」useFetch與要渲染的組件User存在于同一個(gè)組件App中。

          然而,如果User組件內(nèi)還需要進(jìn)一步請(qǐng)求數(shù)據(jù)呢(如下profile數(shù)據(jù))?

          function User({data}{
            const {id, name} = data?.id || {};
            const profile = useFetch(id);
            
            return (
              <div>
                <p>{name}</p>
                {profile ? <Profile data={profile} /> : null}
              </div>

            )
          }

          本著「代碼可維護(hù)性」原則,useFetch與要渲染的組件Profile存在于同一個(gè)組件User中。

          但是,這樣組織代碼,Profile組件只能等User render后再render。

          數(shù)據(jù)只能像瀑布的水一樣,一層一層流下來。

          這種低效的請(qǐng)求數(shù)據(jù)方式被稱為waterfall。

          為了提高「請(qǐng)求效率」,我們可以將“請(qǐng)求Profile組件所需數(shù)據(jù)的操作”提到App組件內(nèi),合并在useFetch中:

          function App({
            const data = useFetch();
            
            return {data ? <User data={data}/> : null};
          }

          但是這樣就降低了「代碼可維護(hù)性」Profile組件離profile數(shù)據(jù)太遠(yuǎn))。

          React團(tuán)隊(duì)從Relay團(tuán)隊(duì)借鑒經(jīng)驗(yàn),借助Suspense特性,提出了Server Components。

          就是為了在處理IO瓶頸時(shí)兼顧「代碼可維護(hù)性」「請(qǐng)求效率」。

          這一特性的實(shí)現(xiàn)需要CM「更新有不同優(yōu)先級(jí)」

          CM為什么花費(fèi)這么久?

          接下來,我們從源碼、特性生態(tài)三個(gè)方面,自底向上看看CM的普及有多么不容易。

          源碼層面

          優(yōu)先級(jí)算法改造

          在v16.13之前,React已經(jīng)實(shí)現(xiàn)了基本的CM功能。

          我們之前聊過,CM有更新優(yōu)先級(jí)的概念。之前是通過一個(gè)毫秒數(shù)expirationTime標(biāo)記「更新」的過期時(shí)間。

          • 通過對(duì)比不同更新的expirationTime判斷優(yōu)先級(jí)高低

          • 通過對(duì)比更新的expirationTime與當(dāng)前時(shí)間判斷更新是否過期(過期需要同步執(zhí)行)

          但是,expirationTime作為一個(gè)與時(shí)間相關(guān)的浮點(diǎn)數(shù),無法表示「一批優(yōu)先級(jí)」這個(gè)概念。

          為了實(shí)現(xiàn)更上層的Server Components特性,需要有「一批優(yōu)先級(jí)」這個(gè)概念。

          于是,核心成員「Andrew Clark」開始了曠日持久的優(yōu)先級(jí)算法改造,見:PR lanes

          Offscreen支持

          在此同時(shí),另一個(gè)成員「Luna Ruan」在開發(fā)一個(gè)新API —— Offscreen。

          可以理解這是React版的Keep-Alive特性。

          訂閱外部源

          未開啟CM前,在一次更新如下三個(gè)生命周期只會(huì)調(diào)用一次:

          • componentWillMount

          • componentWillReceiveProps

          • componentWillUpdate

          但是開啟CM后,由于render階段可能被打斷、重復(fù),所以他們可能被調(diào)用多次。

          在訂閱外部源(比如注冊(cè)事件回調(diào))時(shí),可能更新不及時(shí)或者內(nèi)存泄漏。

          舉個(gè)例子:bindEvent是一個(gè)基于「發(fā)布訂閱」的外部依賴(比如一個(gè)原生DOM事件):

          class App {
            componentWillMount() {
              bindEvent('eventA', data => {
                thie.setState({data});
              });
            }
            componentWillUnmount() {
              bindEvent('eventA');
            }
            render() {
              return <Card data={this.state.data}/>;
            }
          }

          componentWillMount中綁定,在componentWillUnmount中解綁。

          當(dāng)接收到事件后,更新data。

          當(dāng)render階段反復(fù)中斷、暫停后,有可能出現(xiàn):

          事件最終綁定前(bindEvent執(zhí)行前),事件源觸發(fā)了事件

          此時(shí)App組件還未注冊(cè)該事件(bindEvent還未執(zhí)行),那么App獲取的data就是舊的。

          為了解決這個(gè)潛在問題,核心成員「Brian Vaughn」開發(fā)了特性:create-subscription

          用來在React中規(guī)范外部源的訂閱與更新。

          簡(jiǎn)單說就是將外部源的注冊(cè)與更新在commit階段與組件的狀態(tài)更新機(jī)制綁定上。

          特性層面

          當(dāng)「源碼層面」的支持完備后,基于CM的新特性開發(fā)便提上日程。

          這便是Suspense。

          [Umbrella] Releasing Suspense #13206,這個(gè)PR負(fù)責(zé)記錄Suspense特性的進(jìn)展。

          Umbrella標(biāo)記代表這個(gè)PR會(huì)影響非常多庫、組件、工具

          可以看到,長長的時(shí)間線從18年一直到最近幾天。

          最初Suspense只是「前端特性」,當(dāng)時(shí)React SSR只能向前端傳遞「字符串」數(shù)據(jù)(也就是俗稱的脫水

          后來React實(shí)現(xiàn)了一套SSR時(shí)的組件「流式」傳輸協(xié)議,可以「流式」傳輸組件,而不僅僅是HTML字符串。

          此時(shí),Suspense被賦予更多職責(zé)。也擁有了更復(fù)雜的優(yōu)先級(jí),這也是剛才講過的「優(yōu)先級(jí)算法改造」的一大原因。

          最終的成果,就是今年早些時(shí)候推出的Server Components概念。

          生態(tài)層面

          當(dāng)「源碼層面」支持了、「特性」也開發(fā)完成了,是不是就能無縫接入呢?

          還早。

          作為一艘行駛了8年的巨輪,React每次升級(jí)到最終社區(qū)普及,中間都有巨量的工作要做。

          為了幫助社區(qū)慢慢過渡到CM,React做了如下工作:

          • 開發(fā)ScrictMode特性,并且是默認(rèn)啟用的,規(guī)范開發(fā)者寫法

          • componentWillXXX標(biāo)記為unsafe,提醒用戶不要使用,未來會(huì)廢棄

          • 提出了新生命周期(getDerivedStateFromProps、getSnapshotBeforeUpdate)替代如上將被廢棄的生命周期

          • 開發(fā)了legacy模式與CM過渡的中間模式 —— blocking模式

          而這,只是過渡過程中「最簡(jiǎn)單」的部分。

          難的部分是:

          社區(qū)當(dāng)前積累的大量基于legacy模式的庫如何遷移?

          很多動(dòng)畫庫、狀態(tài)管理庫(比如mobX)的遷移并不簡(jiǎn)單。

          總結(jié)

          我們介紹了CM的來龍去脈以及他遷移的難點(diǎn)。

          通過這篇文章,想必你也知道了開頭那個(gè)為React增加createRoot(開啟CM的方法)是多么不容易。

          好在一切都是值得的,如果說以前React的壁壘在于:開源時(shí)間早、社區(qū)規(guī)模大。

          那么從CM開始,React 「可能」會(huì)是前端領(lǐng)域最復(fù)雜的視圖框架。

          屆時(shí),不會(huì)有任何一個(gè)React-like的框架能實(shí)現(xiàn)React同樣的feature。

          但是也有人說,CM帶來的這些功能就是雞肋,我根本不需要。

          你覺得CM怎么樣?歡迎留下你的討論。

          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日韩一级黄色片 | 国产精品无码中文在线 | 欧美三级少妇 | 婷婷操逼| 熟女‖熟女色网精品站 |