成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

「React進(jìn)階」我在函數(shù)組件中可以隨便寫-通俗異步組件原理

開發(fā) 前端
本文通過一些腦洞大開,奇葩的操作,讓大家明白了 Susponse ,componentDidCatch 等原理。我相信不久之后,隨著 React 18 發(fā)布,Susponse 將嶄露頭角,未來可期。

[[428609]]

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

前言

接下來的幾篇文章將圍繞一些‘獵奇’場景,從原理顛覆對 React 的認(rèn)識。每一個場景下背后都透漏出 React 原理,

我可以認(rèn)真的說,看完這篇文章,你將掌握:

1 componentDidCatch 原理

2 susponse 原理

3 異步組件原理。

不可能的事

我的函數(shù)組件中里可以隨便寫,很多同學(xué)看到這句話的時候,腦海里應(yīng)該浮現(xiàn)的四個字是:怎么可能?因?yàn)槲覀冇∠笾械暮瘮?shù)組件,是不能直接使用異步的,而且必須返回一段 Jsx 代碼。

那么今天我將打破這個規(guī)定,在我們認(rèn)為是組件的函數(shù)里做一些意想不到的事情。接下來跟著我的思路往下看吧。

首先先來看一下 jsx ,在 React JSX 中 <div /> 代表 DOM 元素,而 <Index> 代表組件, Index 本質(zhì)是函數(shù)組件或類組件。

  1. <div /> 
  2. <Index /> 

透過現(xiàn)象看本質(zhì),JSX 為 React element 的表象,JSX 語法糖會被 babel 編譯成 React element 對象 ,那么上述中:

  • <div /> 不是真正的 DOM 元素,是 type 屬性為 div 的 element 對象。
  • 組件 Index 是 type 屬性為類或者組件本身的 element 對象。

言歸正傳,那么以函數(shù)組件為參考,Index 已經(jīng)約定俗成為這個樣子:

  1. function Index(){ 
  2.     /* 不能直接的進(jìn)行異步操作 */ 
  3.     /* return 一段 jsx 代碼 */ 
  4.     return <div></div> 

如果不嚴(yán)格按照這個格式寫,通過 jsx 形式掛載,就會報錯。看如下的例子??:

  1. /* Index  不是嚴(yán)格的組件形式 */ 
  2. function Index(){ 
  3.     return { 
  4.        name:'《React進(jìn)階實(shí)踐指南》' 
  5.     } 
  6. /* 正常掛載 Index 組件 */ 
  7. export default class App extends React.Component{ 
  8.     render(){ 
  9.         return <div> 
  10.             hello world , let us learn React!  
  11.             <Index /> 
  12.         </div> 
  13.     } 

我們通過報錯信息,不難發(fā)現(xiàn)原因,children 類型錯誤,children 應(yīng)該是一個 React element 對象,但是 Index 返回的卻是一個普通的對象。

既然不能是普通的對象,那么如果 Index 里面更不可能有異步操作了,比如如下這種情況:

  1. /* 例子2 */ 
  2. unction Index(){ 
  3.    return new Promise((resolve)=>{ 
  4.        setTimeout(()=>{ 
  5.            resolve({ name:'《React進(jìn)階實(shí)踐指南》'  }) 
  6.        },1000) 
  7.    }) 

同樣也會報上面的錯誤,所以在一個標(biāo)準(zhǔn)的 React 組件規(guī)范下:

  • 必須返回 jsx 對象結(jié)構(gòu),不能返回普通對象。
  • render 執(zhí)行過程中,不能出現(xiàn)異步操作。

不可能的事變?yōu)榭赡?/h3>

那么如何破局,將不可能的事情變得可能。首先要解決的問題是 報錯問題 ,只要不報錯,App 就能正常渲染。不難發(fā)現(xiàn)產(chǎn)生的錯誤時機(jī)都是在 render 過程中。那么就可以用 React 提供的兩個渲染錯誤邊界的生命周期 componentDidCatch 和 getDerivedStateFromError。

因?yàn)槲覀円诓东@渲染錯誤之后做一些騷操作,所以這里選 componentDidCatch。接下來我們用 componentDidCatch 改造一下 App。

  1. export default class App extends React.Component{ 
  2.     state = { 
  3.        isError:false 
  4.     } 
  5.     componentDidCatch(e){ 
  6.          this.setState({ isError:true }) 
  7.     } 
  8.     render(){ 
  9.         return <div> 
  10.             hello world , let us learn React! 
  11.             {!this.state.isError &&  <Index />} 
  12.         </div> 
  13.     } 

用 componentDidCatch 捕獲異常,渲染異常

可以看到,雖然還是報錯,但是至少頁面可以正常渲染了。現(xiàn)在做的事情還不夠,以第一 Index 返回一個正常對象為例,我們想要掛載這個組件,還要獲取 Index 返回的數(shù)據(jù),那么怎么辦呢?

突然想到 componentDidCatch 能夠捕獲到渲染異常,那么它的內(nèi)部就應(yīng)該像 try{}catch(){} 一樣,通過 catch 捕獲異常。類似下面這種:

  1. try{ 
  2.     // 嘗試渲染 
  3. }catch(e){ 
  4.      // 渲染失敗,執(zhí)行componentDidCatch(e) 
  5.      componentDidCatch(e)  

那么如果在 Index 中拋出的錯誤,是不是也可以在 componentDidCatch 接收到。于是說干就干。我們把 Index 改變由 return 變成 throw ,然后在 componentDidCatch 打印錯誤 error。

  1. function Index(){ 
  2.     throw { 
  3.        name:'《React進(jìn)階實(shí)踐指南》' 
  4.     } 

將 throw 對象返回。

  1. componentDidCatch(e){ 
  2.     console.log('error:',e) 
  3.     this.setState({ isError:true }) 

通過 componentDidCatch 捕獲錯誤。此時的 e 就是 Index throw 的對象。接下來用子組件拋出的對象渲染。

  1. export default class App extends React.Component{ 
  2.     state = { 
  3.        isError:false
  4.        childThrowMes:{} 
  5.     } 
  6.     componentDidCatch(e){ 
  7.           console.log('error:',e) 
  8.          this.setState({ isError:true , childThrowMes:e }) 
  9.     } 
  10.     render(){ 
  11.         return <div> 
  12.             hello world , let us learn React! 
  13.             {!this.state.isError ?  <Index /> : <div> {this.state.childThrowMes.name} </div>} 
  14.         </div> 
  15.     } 

捕獲到 Index 拋出的異常對象,用對象里面的數(shù)據(jù)重新渲染。

效果:

大功告成,子組件 throw 錯誤,父組件 componentDidCatch 接受并渲染,這波操作是不是有點(diǎn)...

但是 throw 的所有對象,都會被正常捕獲嗎?于是我們把第二個 Index 拋出的 Promise 對象用 componentDidCatch 捕獲。看看會是什么吧?

如上所示,Promise 對象沒有被正常捕獲,捕獲的是異常的提示信息。在異常提示中,可以找到 Suspense 的字樣。那么 throw Promise 和 Suspense 之間肯定存在著關(guān)聯(lián),換句話說就是 Suspense 能夠捕獲到 Promise 對象。而這個錯誤警告,就是 React 內(nèi)部發(fā)出找不到上層的 Suspense 組件的錯誤。

到此為止,可以總結(jié)出:

  • componentDidCatch 通過 try{}catch(e){} 捕獲到異常,如果我們在渲染過程中,throw 出來的普通對象,也會被捕獲到。但是 Promise 對象,會被 React 底層第 2 次拋出異常。
  • Suspense 內(nèi)部可以接受 throw 出來的 Promise 對象,那么內(nèi)部有一個 componentDidCatch 專門負(fù)責(zé)異常捕獲。

鬼畜版——我的組件可以寫異步

即然直接 throw Promise 會在 React 底層被攔截,那么如何在組件內(nèi)部實(shí)現(xiàn)正常編寫異步操作的功能呢?既然 React 會攔截組件拋出的 Promise 對象,那么如果把 Promise 對象包裝一層呢? 于是我們把 Index 內(nèi)容做修改。

  1. function Index(){ 
  2.     throw { 
  3.         current:new Promise((resolve)=>{ 
  4.             setTimeout(()=>{ 
  5.                 resolve({ name:'《React進(jìn)階實(shí)踐指南》'  }) 
  6.             },1000) 
  7.         }) 
  8.     } 

如上,這回不在直接拋出 Promise,而是在 Promise 的外面在包裹一層對象。接下來打印錯誤看一下。

可以看到,能夠直接接收到 Promise 啦,接下來我們執(zhí)行 Promise 對象,模擬異步請求,用請求之后的數(shù)據(jù)進(jìn)行渲染。于是修改 App 組件。

  1. export default class App extends React.Component{ 
  2.     state = { 
  3.        isError:false
  4.        childThrowMes:{} 
  5.     } 
  6.     componentDidCatch(e){ 
  7.          const errorPromise = e.current 
  8.          Promise.resolve(errorPromise).then(res=>{ 
  9.             this.setState({ isError:true , childThrowMes:res }) 
  10.          }) 
  11.     } 
  12.     render(){ 
  13.         return <div> 
  14.             hello world , let us learn React! 
  15.             {!this.state.isError ?  <Index /> : <div> {this.state.childThrowMes.name} </div>} 
  16.         </div> 
  17.     } 

在 componentDidCatch 的參數(shù) e 中獲取 Promise ,Promise.resolve 執(zhí)行 Promise 獲取數(shù)據(jù)并渲染。

效果:

可以看到數(shù)據(jù)正常渲染了,但是面臨一個新的問題:目前的 Index 不是一個真正意義上的組件,而是一個函數(shù),所以接下來,改造 Index 使其變成正常的組件,通過獲取異步的數(shù)據(jù)。

  1. function Index({ isResolve = false , data }){ 
  2.     const [ likeNumber , setLikeNumber ] = useState(0) 
  3.     if(isResolve){ 
  4.         return <div> 
  5.             <p> 名稱:{data.name} </p> 
  6.             <p> star:{likeNumber} </p> 
  7.             <button onClick={()=> setLikeNumber(likeNumber+1)} >點(diǎn)贊</button> 
  8.         </div> 
  9.     }else
  10.         throw { 
  11.             current:new Promise((resolve)=>{ 
  12.                 setTimeout(()=>{ 
  13.                     resolve({ name:'《React進(jìn)階實(shí)踐指南》'  }) 
  14.                 },1000) 
  15.             }) 
  16.         } 
  17.     } 
  • Index 中通過 isResolve 判斷組件是否加在完成,第一次的時候 isResolve = false 所以 throw Promise。
  • 父組件 App 中接受 Promise ,得到數(shù)據(jù),改變狀態(tài) isResolve ,二次渲染,那么第二次 Index 就會正常渲染了。看一下 App 如何寫:
  1. export default class App extends React.Component{ 
  2.     state = { 
  3.        isResolve:false
  4.        data:{} 
  5.     } 
  6.     componentDidCatch(e){ 
  7.          const errorPromise = e.current 
  8.          Promise.resolve(errorPromise).then(res=>{ 
  9.             this.setState({ data:res,isResolve:true  }) 
  10.          }) 
  11.     } 
  12.     render(){ 
  13.         const {  isResolve ,data } = this.state 
  14.         return <div> 
  15.             hello world , let us learn React! 
  16.             <Index data={data} isResolve={isResolve} /> 
  17.         </div> 
  18.     } 

通過 componentDidCatch 捕獲錯誤,然后進(jìn)行第二次渲染。

效果:

達(dá)到了目的。這里就簡單介紹了一下異步組件的原理。上述引入了一個 Susponse 的概念,接下來研究一下 Susponse。

飛翔版——實(shí)現(xiàn)一個簡單 Suspense

Susponse 是什么?Susponse 英文翻譯 懸停。在 React 中 Susponse 是什么呢?那么正常情況下組件染是一氣呵成的,在 Susponse 模式下的組件渲染就變成了可以先懸停下來。

首先解釋為什么懸停?

Susponse 在 React 生態(tài)中的位置,重點(diǎn)體現(xiàn)在以下方面。

  • code splitting(代碼分割) :哪個組件加載,就加載哪個組件的代碼,聽上去挺拗口,可確實(shí)打?qū)嵉慕鉀Q了主文件體積過大的問題,間接優(yōu)化了項(xiàng)目的首屏加載時間,我們知道過瀏覽器加載資源也是耗時的,這些時間給用戶造成的影響就是白屏效果。
  • spinner 解耦:正常情況下,頁面展示是需要前后端交互的,數(shù)據(jù)加載過程不期望看到 無數(shù)據(jù)狀態(tài)->閃現(xiàn)數(shù)據(jù)的場景,更期望的是一種spinner數(shù)據(jù)加載狀態(tài)->加載完成展示頁面狀態(tài)。比如如下結(jié)構(gòu):
  1. <List1 /> 
  2. <List2 /> 

List1 和 List2 都使用服務(wù)端請求數(shù)據(jù),那么在加載數(shù)據(jù)過程中,需要 Spin 效果去優(yōu)雅的展示 UI,所以需要一個 Spin 組件,但是 Spin 組件需要放入 List1 和 List2 的內(nèi)部,就造成耦合關(guān)系。現(xiàn)在通過 Susponse 來接耦 Spin,在業(yè)務(wù)代碼中這么寫道:

  1. <Suspense fallback={ <Spin /> }  > 
  2.     <List1 /> 
  3.     <List2 /> 
  4. </Suspense> 

 

當(dāng) List1 和 List2 數(shù)據(jù)加載過程中,用 Spin 來 loading 。把 Spin 解耦出來,就像看電影,如果電影加載視頻流卡住,不期望給用戶展示黑屏幕,取而代之的是用海報來填充屏幕,而海報就是這個 Spin 。

  • render data:整個 render 過程都是同步執(zhí)行一氣呵成的,那樣就會 組件 Render => 請求數(shù)據(jù) => 組件 reRender ,但是在 Suspense 異步組件情況下允許調(diào)用 Render => 發(fā)現(xiàn)異步請求 => 懸停,等待異步請求完畢 => 再次渲染展示數(shù)據(jù)。這樣無疑減少了一次渲染。

接下來解釋如何懸停

上面理解了 Suspense 初衷,接下來分析一波原理,首先通過上文中,已經(jīng)交代了 Suspense 原理,如何懸停,很簡單粗暴,直接拋出一個異常;

異常是什么,一個 Promise ,這個 Promise 也分為二種情況:

  • 第一種就是異步請求數(shù)據(jù),這個 Promise 內(nèi)部封裝了請求方法。請求數(shù)據(jù)用于渲染。
  • 第二種就是異步加載組件,配合 webpack 提供的 require() api,實(shí)現(xiàn)代碼分割。

懸停后再次render

在 Suspense 懸停后,如果想要恢復(fù)渲染,那么 rerender 一下就可以了。

如上詳細(xì)介紹了 Suspense 。接下來到了實(shí)踐環(huán)節(jié),我們?nèi)L試實(shí)現(xiàn)一個 Suspense ,首先聲明一下這個 Suspense 并不是 React 提供的 Suspense ,這里只是模擬了一下它的大致實(shí)現(xiàn)細(xì)節(jié)。

本質(zhì)上 Suspense 落地瓶頸也是對請求函數(shù)的的封裝,Suspense 主要接受 Promise,并 resolve 它,那么對于成功的狀態(tài)回傳到異步組件中,對于開發(fā)者來說是未知的,對于 Promise 和狀態(tài)傳遞的函數(shù) createFetcher,應(yīng)該滿足如下的條件。

  1. const fetch = createFetcher(function getData(){ 
  2.     return new Promise((resolve)=>{ 
  3.        setTimeout(()=>{ 
  4.             resolve({ 
  5.                 name:'《React進(jìn)階實(shí)踐指南》'
  6.                 author:'alien' 
  7.             }) 
  8.        },1000) 
  9.     }) 
  10. }) 
  11. function Text(){ 
  12.     const data = fetch() 
  13.     return <div> 
  14.         name: {data.name
  15.         author:{data.author} 
  16.     </div> 
  • 通過 createFetcher 封裝請求函數(shù)。請求函數(shù) getData 返回一個 Promise ,這個 Promise 的使命就是完成數(shù)據(jù)交互。
  • 一個模擬的異步組件,內(nèi)部使用 createFetcher 創(chuàng)建的請求函數(shù),請求數(shù)據(jù)。

接下來就是 createFetcher 函數(shù)的編寫。

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

既然有了 createFetcher 函數(shù),接下來就要模擬上游組件 Susponse 。

  1. class MySusponse extends React.Component{ 
  2.     state={ 
  3.         isResolve:true 
  4.     } 
  5.     componentDidCatch(fetcher){ 
  6.         const p = fetcher.p 
  7.         this.setState({ isResolve:false }) 
  8.         Promise.resolve(p).then(()=>{ 
  9.             this.setState({ isResolve:true }) 
  10.         }) 
  11.     } 
  12.     render(){ 
  13.         const { fallback, children  } = this.props 
  14.         const { isResolve } = this.state 
  15.         return isResolve ? children : fallback 
  16.     } 

我們編寫的 Susponse 起名字叫 MySusponse 。

  • MySusponse 內(nèi)部 componentDidCatch 通過 Promise.resolve 捕獲 Promise 成功的狀態(tài)。成功后,取締 fallback UI 效果。

大功告成,接下來就是體驗(yàn)環(huán)節(jié)了。我們嘗試一下 MySusponse 效果。

  1. export default function Index(){ 
  2.     return <div> 
  3.         hello,world 
  4.        <MySusponse fallback={<div>loading...</div>} > 
  5.             <Text /> 
  6.        </MySusponse> 
  7.     </div> 

效果:

雖然實(shí)現(xiàn)了效果,但是和真正的 Susponse 還差的很遠(yuǎn),首先暴露出的問題就是數(shù)據(jù)可變的問題。上述編寫的 MySusponse 數(shù)據(jù)只加載一次,但是通常情況下,數(shù)據(jù)交互是存在變數(shù)的,數(shù)據(jù)也是可變的。

衍生版——實(shí)現(xiàn)一個錯誤異常處理組件

言歸正傳,我們不會在函數(shù)組件中做如上的騷操作,也不會自己去編寫 createFetcher 和 Susponse。但是有一個場景還是蠻實(shí)用的,那就是對渲染錯誤的處理,以及 UI 的降級,這種情況通常出現(xiàn)在服務(wù)端數(shù)據(jù)的不確定的場景下,比如我們通過服務(wù)端的數(shù)據(jù) data 進(jìn)行渲染,像如下場景:

  1. <div>{ data.name }</div> 

 

如果 data 是一個對象,那么會正常渲染,但是如果 data 是 null,那么就會報錯,如果不加渲染錯誤邊界,那么一個小問題會導(dǎo)致整個頁面都渲染不出來。

那么對于如上情況,如果每一個頁面組件,都加上 componentDidCatch 這樣捕獲錯誤,降級 UI 的方式,那么代碼過于冗余,難以復(fù)用,無法把降級的 UI 從業(yè)務(wù)組件中解耦出來。

所以可以統(tǒng)一寫一個 RenderControlError 組件,目的就是在組件的出現(xiàn)異常的情況,統(tǒng)一展示降級的 UI ,也確保了整個前端應(yīng)用不會奔潰,同樣也讓服務(wù)端的數(shù)據(jù)格式容錯率大大提升。接下來看一下具體實(shí)現(xiàn)。

  1. class RenderControlError extends React.Component{ 
  2.     state={ 
  3.         isError:false 
  4.     } 
  5.     componentDidCatch(){ 
  6.         this.setState({ isError:true }) 
  7.     } 
  8.     render(){ 
  9.         return !this.state.isError ? 
  10.              this.props.children : 
  11.              <div style={styles.errorBox} > 
  12.                  <img url={require('../../assets/img/error.png')} 
  13.                      style={styles.erroImage} 
  14.                  /> 
  15.                  <span style={styles.errorText}  >出現(xiàn)錯誤</span> 
  16.              </div> 
  17.     } 

如果 children 出錯,那么降級 UI。

使用

 

總結(jié)

本文通過一些腦洞大開,奇葩的操作,讓大家明白了 Susponse ,componentDidCatch 等原理。我相信不久之后,隨著 React 18 發(fā)布,Susponse 將嶄露頭角,未來可期。

 

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

2021-06-07 08:41:59

React異步組件

2024-11-25 07:00:00

箭頭函數(shù)JavaScriptReact

2021-09-05 07:35:58

lifecycleAndroid組件原理

2021-07-08 06:51:29

React函數(shù)組件

2022-07-06 08:29:12

antdInput 組件

2022-02-16 08:11:52

組件渲染前端

2022-04-26 05:55:06

Vue.js異步組件

2025-03-06 11:07:27

2019-07-22 10:42:11

React組件前端

2020-10-21 08:38:47

React源碼

2021-02-26 15:10:00

前端React組件交互

2021-10-15 14:28:30

React 組件渲染

2024-05-08 08:16:27

React函數(shù)式組件純函數(shù)

2021-07-15 07:23:25

React動畫頁面

2020-05-20 14:25:45

Reactreact.js前端

2021-03-18 08:00:55

組件Hooks React

2017-05-17 15:50:34

開發(fā)前端react

2025-04-07 08:25:01

React復(fù)合組件組件模式

2023-11-21 07:17:36

Reac函數(shù)組件

2021-04-27 11:28:21

React.t事件元素
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 天天搞天天操 | 久久一热 | 91一区二区在线观看 | 国产精品日韩一区二区 | 国产精品久久久久久久毛片 | 国产精品久久av | 欧美午夜精品 | 高清国产午夜精品久久久久久 | 中文字幕乱码一区二区三区 | 国产一区91精品张津瑜 | 免费看av大片 | 免费在线观看h片 | 欧美日韩不卡合集视频 | 久久69精品久久久久久国产越南 | 久久夜视频| 成人免费观看视频 | 国产永久免费 | 欧美xxxx性xxxxx高清 | 久久99久久| 国产农村妇女毛片精品久久麻豆 | 国产精品美女久久久 | 四虎影视免费在线 | 国产精品1区2区 | 九色91视频 | 成人免费在线 | 久久一区二区三区免费 | 在线免费黄色 | 国产精品不卡 | 成人高清视频在线观看 | 午夜精品一区二区三区在线视频 | 精品国产乱码久久久久久丨区2区 | 色偷偷人人澡人人爽人人模 | 欧美日韩三级在线观看 | 中文字幕亚洲视频 | a毛片视频网站 | 久久久999免费视频 999久久久久久久久6666 | 日韩成人一区二区 | caoporn视频在线 | 国产精品不卡一区 | 中文字幕免费视频 | 暖暖日本在线视频 |