<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,不遠啦?

          共 5504字,需瀏覽 12分鐘

           ·

          2021-06-11 04:00



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

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

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

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

          可以說從18年到21年,React團隊的主要工作就是圍繞CM展開的,那么:

          • CM是什么?

          • CM能解決React什么問題?

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

          本文將作出解答。

          CM是什么

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

          React大體可以分為兩個工作階段:

          • render階段

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

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

          • commit階段

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

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

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

          在該模式下一次render階段對應一次commit階段。

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

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

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

          可能多次render階段對應一次commit階段。

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

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

          不同模式支持的特性

          為什么需要CM?

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

          這得從React的設計理念聊起。

          我們可以從官網(wǎng)React哲學看到React的設計理念:

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

          其中「快速響應」是重點。

          那么什么影響「快速響應」呢?React團隊給出的答案:

          CPU的瓶頸和IO的瓶頸

          CPU的瓶頸

          考慮如下demo,我們渲染3000的列表項:

          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階段不會被打斷,則這3000個lirender都得在同一個瀏覽器宏任務中完成。

          長時間的計算會阻塞線程,造成頁面掉幀,這就是CPU的瓶頸。

          解決的辦法就是:啟用CM,將render階段變?yōu)?strong style="color: rgb(145, 109, 213);">「可中斷」的,

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

          IO的瓶頸

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

          IO瓶頸是客觀存在的。

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

          但是,通常情況下:「代碼可維護性」「請求效率」是相悖的。

          什么意思呢,舉個例子:

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

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

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

          然而,如果User組件內(nèi)還需要進一步請求數(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>

            )
          }

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

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

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

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

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

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

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

          React團隊從Relay團隊借鑒經(jīng)驗,借助Suspense特性,提出了Server Components。

          就是為了在處理IO瓶頸時兼顧「代碼可維護性」「請求效率」。

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

          CM為什么花費這么久?

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

          源碼層面

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

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

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

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

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

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

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

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

          Offscreen支持

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

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

          訂閱外部源

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

          • componentWillMount

          • componentWillReceiveProps

          • componentWillUpdate

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

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

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

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

          componentWillMount中綁定,在componentWillUnmount中解綁。

          當接收到事件后,更新data。

          render階段反復中斷、暫停后,有可能出現(xiàn):

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

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

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

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

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

          特性層面

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

          這便是Suspense

          [Umbrella] Releasing Suspense #13206,這個PR負責記錄Suspense特性的進展。

          Umbrella標記代表這個PR會影響非常多庫、組件、工具

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

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

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

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

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

          生態(tài)層面

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

          還早。

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

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

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

          • componentWillXXX標記為unsafe,提醒用戶不要使用,未來會廢棄

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

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

          而這,只是過渡過程中「最簡單」的部分。

          難的部分是:

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

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

          總結(jié)

          我們介紹了CM的來龍去脈以及他遷移的難點。

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

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

          那么從CM開始,React 「可能」會是前端領域最復雜的視圖框架。

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

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

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






          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美老女人操持 | 天天操夜夜操狠狠 | 欧美视频一区 | 黄色电影一区 | 亚洲大鸡吧 |