自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

「React進(jìn)階」漫談React異步組件前世與今生

開發(fā) 前端
本文講了React Susponse的由來,實(shí)現(xiàn)原理,目前階段狀態(tài),以及未來的展望,對于React前世與今生,你有什么看法呢?

[[404042]]

本文轉(zhuǎn)載自微信公眾號「前端Sharing」,作者前端Sharing。轉(zhuǎn)載本文請聯(lián)系前端Sharing公眾號。

一 前言

今天我們聊一聊React中的異步組件的現(xiàn)況和未來,異步組件很可能是未來從數(shù)據(jù)交互到UI展示一種流暢的技術(shù)方案,所以既然要吃透React,進(jìn)階React,就有必要搞懂異步組件。

老規(guī)矩,我們還是帶著問題開始今天的思考?(自測掌握程度)

  • 1 什么是React異步組件,解決什么問題?
  • 2 componentDidCatch如何捕獲到渲染階段錯誤,又這么彌補(bǔ)。
  • 3 React.lazy如何實(shí)現(xiàn)動態(tài)加載的?
  • 4 React.lazy為什么要在Supsonse內(nèi)部。
  • 5 Supsonse原理是什么?

二 初識:異步組件

1 什么是異步組件

我們先來想想目前的React應(yīng)用中使用ajax或者fetch進(jìn)行數(shù)據(jù)交互場景,基本上就是這樣的,在類組件中componentDidMount和函數(shù)組件effect中進(jìn)行數(shù)據(jù)交互,得到數(shù)據(jù)后,再渲染UI視圖。那么可不可以讓組件的渲染等待異步數(shù)據(jù)請求完畢,得到數(shù)據(jù)后再進(jìn)行render呢?

對于上面這種情況,第一感覺是難以置信,如果能夠?qū)崿F(xiàn)讓渲染中斷,等到數(shù)據(jù)請求之后,再渲染呢?那就是Susponse,上面說到的不可能實(shí)現(xiàn)的事,Susponse做到了,React 16.6 新增了,Susponse 讓組件“等待”某個異步操作,直到該異步操作結(jié)束即可渲染。

傳統(tǒng)模式:渲染組件-> 請求數(shù)據(jù) -> 再渲染組件。

異步模式:請求數(shù)據(jù)-> 渲染組件。

2 開啟Suspense模式

一個傳統(tǒng)模式下的數(shù)據(jù)交互應(yīng)該是這個樣子的。

  1. function Index(){ 
  2.     const [ userInfo , setUserInfo ] = React.useState(0) 
  3.     React.useEffect(()=>{ 
  4.        /* 請求數(shù)據(jù)交互 */ 
  5.        getUserInfo().then(res=>{ 
  6.            setUserInfo(res) 
  7.        }) 
  8.     },[]) 
  9.     return <div> 
  10.         <h1>{userInfo.name}</h1>; 
  11.     </div> 
  12. export default function Home(){ 
  13.     return <div> 
  14.         <Index /> 
  15.     </div> 

流程:頁面初始化掛載,useEffect里面請求數(shù)據(jù),通過useState改變數(shù)據(jù),二次更新組件渲染數(shù)據(jù)。

那么如果用Susponse異步模式就可以這么寫:

  1. function FutureAsyncComponent (){ 
  2.     const userInfo = getUserInfo() 
  3.     return <div> 
  4.         <h1>{userInfo.name}</h1>;  
  5.     </div> 
  6.  
  7. /* 未來的異步模式 */ 
  8. export default function Home(){ 
  9.    return <div> 
  10.       <React.Suspense  fallback={ <div  > loading... </div> } > 
  11.           <FutureAsyncComponent/> 
  12.       </React.Suspense> 
  13.    </div> 

當(dāng)數(shù)據(jù)還沒有加載完成時候,會展示Suspense中 fallback的內(nèi)容,彌補(bǔ)請求數(shù)據(jù)中過渡效果 ,盡管這個模式在現(xiàn)在版本中還不能正式使用,但是將來 React 會支持這樣的代碼形式。

三 溯源:從componentDidCatch到Suspense

至于Suspense是如何將上述不可能的事情變成可能的呢?這就要從 componentDidCatch 說起了,在 React 推出 v16 的時候,就增加了一個新生命周期函數(shù) componentDidCatch。如果某個組件定義了 componentDidCatch,那么這個組件中所有的子組件在渲染過程中拋出異常時,這個 componentDidCatch 函數(shù)就會被調(diào)用。

componentDidCatch使用

componentDidCatch 可以捕獲異常,它接受兩個參數(shù):

1 error —— 拋出的錯誤。

2 info —— 帶有 componentStack key 的對象,其中包含有關(guān)組件引發(fā)錯誤的棧信息。

我們來模擬一個子組件渲染失敗的情況:

  1. /* 正常組件,可以渲染 */ 
  2. function Children(){ 
  3.   return <div> hello ,let us learn React </div> 
  4.  /* 非React組件,將無法正常渲染 */ 
  5. function Children1(){ 
  6.   return  
  7. export default class Index extends React.Component{ 
  8.   componentDidCatch(error,info){ 
  9.       console.log(error,info) 
  10.   } 
  11.   render(){ 
  12.     return <div> 
  13.       <Children /> 
  14.       <Children1/> 
  15.     </div> 
  16.   } 

如上,我們模擬一個render失敗的場景,將一個非React組件Children1當(dāng)作正常的React的組件來渲染,這樣在渲染階段就會報錯,錯誤信息就會被 componentDidCatch捕獲到,錯誤信息如下:

對于如上如果在渲染子組件的時候出現(xiàn)錯誤,會導(dǎo)致整個組件渲染失敗,無法顯示,正常的組件Children也會被牽連,這個時候我們需要在componentDidCatch做一些補(bǔ)救措施,比如我們發(fā)現(xiàn) componentDidCatch 失敗,可以給Children1加一個狀態(tài)控制,如果渲染失敗,那么終止Children1的render。

  1. function ErroMessage(){ 
  2.   return <div>渲染出現(xiàn)錯誤~</div> 
  3.  
  4. export default class Index extends React.Component{ 
  5.   state={ errorRender:false } 
  6.   componentDidCatch(error,info){ 
  7.       /* 補(bǔ)救措施 */ 
  8.       this.setState({ 
  9.         errorRender:true 
  10.       }) 
  11.   } 
  12.   render(){ 
  13.     return <div> 
  14.       <Children /> 
  15.       { this.state.errorRender ? <ErroMessage/> : <Children1/>  } 
  16.     </div> 
  17.   } 

如果出現(xiàn)錯誤,通過setState重新渲染,并移除失敗的組件,這樣組件就能正常渲染了,同樣也不影響Children掛載。componentDidCatch一方面捕獲在渲染階段出現(xiàn)的錯誤,另一方面可以在生命周期的內(nèi)部執(zhí)行副作用去挽回渲染異常帶來的損失。

componentDidCatch原理

componentDidCatch原理應(yīng)該很好理解,內(nèi)部可以通過try{}catch(error){}來捕獲渲染錯誤,處理渲染錯誤。

  1. try { 
  2.   //嘗試渲染子組件 
  3. } catch (error) { 
  4.   // 出現(xiàn)錯誤,componentDidCatch被調(diào)用, 

componentDidCatch思想能否遷移到Suspense上

那么回到我們的異步組件上來,如果讓異步的代碼放在同步執(zhí)行,是肯定不會正常的渲染的,我們還是要先請求數(shù)據(jù),等到數(shù)據(jù)返回,再用返回的數(shù)據(jù)進(jìn)行渲染,那么重點(diǎn)在于這個等字,如何讓同步的渲染停止下來,去等異步的數(shù)據(jù)請求呢?拋出異??梢詥? 異常可以讓代碼停止執(zhí)行,當(dāng)然也可以讓渲染中止。

Suspense 就是用拋出異常的方式中止的渲染,Suspense 需要一個 createFetcher 函數(shù)會封裝異步操作,當(dāng)嘗試從 createFetcher 返回的結(jié)果讀取數(shù)據(jù)時,有兩種可能:一種是數(shù)據(jù)已經(jīng)就緒,那就直接返回結(jié)果;還有一種可能是異步操作還沒有結(jié)束,數(shù)據(jù)沒有就緒,這時候 createFetcher 會拋出一個“異常”。

這個“異常”是正常的代碼錯誤嗎?非也,這個異常是封裝請求數(shù)據(jù)的Promise對象,里面是真正的數(shù)據(jù)請求方法,既然 Suspense 能夠拋出異常,就能夠通過類似 componentDidCatch的try{}catch{}去獲取這個異常。

獲取這個異常之后干什么呢? 我們知道這個異常是Promise,那么接下來當(dāng)然是執(zhí)行這個Promise,在成功狀態(tài)后,獲取數(shù)據(jù),然后再次渲染組件,此時的渲染就已經(jīng)讀取到正常的數(shù)據(jù),那么可以正常的渲染了。接下來我們模擬一下createFetcher和Suspense

我們模擬一個簡單createFetcher

  1. /** 
  2.  *  
  3.  * @param {*} fn  我們請求數(shù)據(jù)交互的函數(shù),返回一個數(shù)據(jù)請求的Promise  
  4.  */ 
  5. function createFetcher(fn){ 
  6.     const fetcher = { 
  7.         status:'pedding'
  8.         result:null
  9.         p:null 
  10.     } 
  11.     return function (){ 
  12.       const getDataPromise = fn() 
  13.       fetcher.p = getDataPromise 
  14.       getDataPromise.then(result=>{ /* 成功獲取數(shù)據(jù) */ 
  15.          fetcher.result = result  
  16.          fetcher.status = 'resolve' 
  17.       }) 
  18.    
  19.       if(fetcher.status === 'pedding'){ /* 第一次執(zhí)行中斷渲染,第二次 */ 
  20.          throw fetcher 
  21.       } 
  22.       /* 第二次執(zhí)行 */ 
  23.       if(fetcher.status) 
  24.       return fetcher.result 
  25.     } 
  • 返回一個函數(shù),在渲染階段執(zhí)行,第一次組件渲染,由于 status = pedding 所以拋出異常 fetcher 給 Susponse,渲染中止。
  • Susponse會在內(nèi)部componentDidCatch處理這個fetcher,執(zhí)行 getDataPromise.then, 這個時候status已經(jīng)是resolve狀態(tài),數(shù)據(jù)也能正常返回了。
  • 接下來Susponse再次渲染組件,此時,此時就能正常的獲取數(shù)據(jù)了。

我們模擬一個簡單的Suspense

  1. export class Suspense extends React.Component{ 
  2.    state={ isRender: true  } 
  3.    componentDidCatch(e){ 
  4.      /* 異步請求中,渲染 fallback */ 
  5.      this.setState({ isRender:false }) 
  6.      const { p } = e 
  7.      Promise.resolve(p).then(()=>{ 
  8.        /* 數(shù)據(jù)請求后,渲染真實(shí)組件 */ 
  9.        this.setState({ isRender:true }) 
  10.      }) 
  11.    } 
  12.    render(){ 
  13.      const { isRender } = this.state 
  14.      const { children , fallback } = this.props 
  15.      return isRender ? children : fallback 
  16.    } 

用 componentDidCatch 捕獲異步請求,如果有異步請求渲染 fallback,等到異步請求執(zhí)行完畢,渲染真實(shí)組件,借此整個異步流程完畢。但為了讓大家明白流程,只是一次模擬異步的過程,實(shí)際流程要比這個復(fù)雜的多。

流程圖:

四 實(shí)踐:從Suspense到React.lazy

React.lazy簡介

Suspense帶來的異步組件的革命還沒有一個實(shí)質(zhì)性的成果,目前版本沒有正式投入使用,但是React.lazy是目前版本Suspense的最佳實(shí)踐。我們都知道React.lazy配合Suspense可以實(shí)現(xiàn)懶加載,按需加載,這樣很利于代碼分割,不會讓初始化的時候加載大量的文件,減少首屏?xí)r間。

React.lazy基本使用

  1. const LazyComponent = React.lazy(()=>import('./text')) 

React.lazy接受一個函數(shù),這個函數(shù)需要動態(tài)調(diào)用 import()。它必須返回一個 Promise ,該 Promise需要 resolve 一個 default export 的 React 組件。

我們先來看一下基本使用:

  1. const LazyComponent = React.lazy(() => import('./test.js')) 
  2.  
  3. export default function Index(){ 
  4.    return <Suspense fallback={<div>loading...</div>} > 
  5.        <LazyComponent /> 
  6.    </Suspense> 

我們用Promise模擬一下 import()效果,將如上 LazyComponent改成如下的樣子:

  1. function Test(){ 
  2.   return <div className="demo"  >《React進(jìn)階實(shí)踐指南》即將上線~</div> 
  3. const LazyComponent =  React.lazy(()=> new Promise((resolve)=>{ 
  4.   setTimeout(()=>{ 
  5.       resolve({ 
  6.           default: ()=> <Test /> 
  7.       }) 
  8.   },2000) 
  9. })) 

效果:

React.lazy原理解讀

React.lazy 是如何配合Susponse 實(shí)現(xiàn)動態(tài)加載的效果的呢?實(shí)際上,lazy內(nèi)部就是做了一個createFetcher,而上面講到createFetcher得到渲染的數(shù)據(jù),而lazy里面自帶的createFetcher異步請求的是組件。lazy內(nèi)部模擬一個promiseA規(guī)范場景。我們完全可以理解React.lazy用Promise模擬了一個請求數(shù)據(jù)的過程,但是請求的結(jié)果不是數(shù)據(jù),而是一個動態(tài)的組件。

接下來我們看一下lazy是如何處理的

  1. function lazy(ctor){ 
  2.     return { 
  3.          $$typeof: REACT_LAZY_TYPE, 
  4.          _payload:{ 
  5.             _status: -1,  //初始化狀態(tài) 
  6.             _result: ctor, 
  7.          }, 
  8.          _init:function(payload){ 
  9.              if(payload._status===-1){ /* 第一次執(zhí)行會走這里  */ 
  10.                 const ctor = payload._result; 
  11.                 const thenable = ctor(); 
  12.                 payload._status = Pending; 
  13.                 payload._result = thenable; 
  14.                 thenable.then((moduleObject)=>{ 
  15.                     const defaultExport = moduleObject.default
  16.                     resolved._status = Resolved; // 1 成功狀態(tài) 
  17.                     resolved._result = defaultExport;/* defaultExport 為我們動態(tài)加載的組件本身  */  
  18.                 }) 
  19.              } 
  20.             if(payload._status === Resolved){ // 成功狀態(tài) 
  21.                 return payload._result; 
  22.             } 
  23.             else {  //第一次會拋出Promise異常給Suspense 
  24.                 throw payload._result;  
  25.             } 
  26.          } 
  27.     } 

React.lazy包裹的組件會標(biāo)記REACT_LAZY_TYPE類型的element,在調(diào)和階段會變成 LazyComponent 類型的fiber,React對LazyComponent會有單獨(dú)的處理邏輯,第一次渲染首先會執(zhí)行 _init 方法。此時這個_init方法就可以理解成createFetcher。

我們看一下lazy中init函數(shù)的執(zhí)行:

react-reconciler/src/ReactFiberBeginWork.js

  1. function mountLazyComponent(){ 
  2.     const init = lazyComponent._init; 
  3.     let Component = init(payload); 
  • 如上在mountLazyComponent初始化的時候執(zhí)行 _init 方法,里面會執(zhí)行l(wèi)azy的第一個函數(shù),得到一個Promise,綁定 Promise.then成功回調(diào),回調(diào)里得到我們組件 defaultExport,這里要注意的是,如上面的函數(shù)當(dāng)?shù)诙€if判斷的時候,因?yàn)榇藭r狀態(tài)不是 Resolved ,所以會走else,拋出異常 Promise,拋出異常會讓當(dāng)前渲染終止。
  • Susponse內(nèi)部處理這個promise,然后再一次渲染組件,下一次渲染就直接渲染這個組件。達(dá)到了動態(tài)加載的目的。

流程圖

五 展望:Suspense未來可期

你當(dāng)下并不使用 Relay,那么你暫時無法在應(yīng)用中試用 Suspense。因?yàn)槠駷橹梗趯?shí)現(xiàn)了 Suspense 的庫中,Relay 是我們唯一在生產(chǎn)環(huán)境測試過,且對它的運(yùn)作有把握的一個庫。

目前Suspense還并不能,如果你想使用,可以嘗試一下在生產(chǎn)環(huán)境使用集成了 Suspense 的 Relay。Relay 指南!

Suspense能解決什么?

  • Suspense讓數(shù)據(jù)獲取庫與 React 緊密整合。如果一個數(shù)據(jù)請求庫實(shí)現(xiàn)了對 Suspense 的支持,那么,在 React 中使用 Suspense 將會是自然不過的事。
  • Suspense能夠自由的展現(xiàn),請求中的加載效果。能讓視圖加載有更主動的控制權(quán)。
  • Suspense能夠讓請求數(shù)據(jù)到渲染更流暢靈活,我們不用在componentDidMount請求數(shù)據(jù),再次觸發(fā)render,一切交給Suspense解決,一氣呵成。

Suspense面臨挑戰(zhàn)?

對于未來的Suspense能否作為主流異步請求數(shù)據(jù)渲染的方案,筆者認(rèn)為Suspense未來還是充滿期待,那么對于Suspense的挑戰(zhàn),個人感覺在于以下幾個方面:

1 concurrent模式下的Susponse可以帶來更好的用戶體驗(yàn),react團(tuán)隊(duì)能夠讓未來的Suspense更靈活,有一套更清晰明確的createFetcher制作手冊,是未來的concurrent模式下Suspense脫穎而出的關(guān)鍵。

2 Suspense能否廣泛使用,更在于 Suspense 的生態(tài)發(fā)展,有一個穩(wěn)定的數(shù)據(jù)請求庫與Suspense完美契合。

3 開發(fā)者對Suspense的價值的認(rèn)可,如果Suspense在未來的表現(xiàn)力更出色的話,會有更多開發(fā)者寧愿自己封裝一套數(shù)據(jù)請求方法,給優(yōu)秀的Suspense買單。

六 總結(jié)

本文講了React Susponse的由來,實(shí)現(xiàn)原理,目前階段狀態(tài),以及未來的展望,對于React前世與今生,你有什么看法呢?

 

責(zé)任編輯:武曉燕 來源: 前端Sharing
相關(guān)推薦

2022-05-10 09:14:15

React 并發(fā)渲染

2010-05-07 09:33:28

GNU GPL開源許可協(xié)議

2021-10-13 14:01:00

函數(shù)React進(jìn)階

2013-05-23 13:40:48

OpenStackKVM區(qū)別

2022-06-28 18:47:38

WiFi5G

2021-04-27 11:28:21

React.t事件元素

2022-08-15 17:34:22

react-routv6

2019-07-22 10:42:11

React組件前端

2011-08-23 09:52:31

CSS

2025-02-12 11:25:39

2014-07-30 10:55:27

2015-11-18 14:14:11

OPNFVNFV

2020-01-11 17:21:51

區(qū)塊鏈數(shù)字貨幣比特幣

2020-01-10 07:20:52

區(qū)塊鏈起源與發(fā)展

2015-04-14 10:33:53

2021-03-18 08:00:55

組件Hooks React

2017-05-17 15:50:34

開發(fā)前端react

2016-11-08 19:19:06

2016-11-03 13:33:31

2016-12-29 13:34:04

阿爾法狗圍棋計算機(jī)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號