日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
性能優(yōu)化竟白屏,難道真是我的鍋?

本文轉(zhuǎn)載自微信公眾號(hào)「DYBOY」,作者DYBOY。轉(zhuǎn)載本文請(qǐng)聯(lián)系DYBOY公眾號(hào)。

創(chuàng)新互聯(lián)公司是網(wǎng)站建設(shè)技術(shù)企業(yè),為成都企業(yè)提供專業(yè)的成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè),網(wǎng)站設(shè)計(jì),網(wǎng)站制作,網(wǎng)站改版等技術(shù)服務(wù)。擁有十余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制適合企業(yè)的網(wǎng)站。十余年品質(zhì),值得信賴!

隨著項(xiàng)目日漸“強(qiáng)壯”,優(yōu)化首屏加載渲染速度迫在眉睫,其中就采用了 React 框架提供的 Reat.lazy() 按需加載的方式,測(cè)試過程中,在我們的埋點(diǎn)監(jiān)控平臺(tái)上,發(fā)現(xiàn)了很多網(wǎng)絡(luò)請(qǐng)求錯(cuò)誤的日志,大部分來自分包資源下載失敗!難道我的優(yōu)化變成負(fù)優(yōu)化了?

通過我們的統(tǒng)計(jì)平臺(tái)量化數(shù)據(jù)可知,用戶網(wǎng)絡(luò)加載失敗的概率還是比較大,實(shí)驗(yàn)發(fā)現(xiàn),沒法兒使用 try{}catch{} 捕獲組件渲染錯(cuò)誤,查詢官方文檔,有一個(gè) Error Boundaries 的組件引入眼簾,提供了解決方法,那我們拿到了 demo 應(yīng)該怎么完善并應(yīng)用到我們的項(xiàng)目中,以及如何解決按需加載組件失敗的場(chǎng)景。

背景

某天我在開發(fā)了某個(gè)功能組件時(shí),發(fā)現(xiàn)這個(gè)組件引用了一個(gè)非常大的三方庫,大概100kb,這么大,當(dāng)然得使用按需加載啦,當(dāng)我理所當(dāng)然地覺得這一手“按需加載”的優(yōu)化很穩(wěn),就交給測(cè)試同學(xué)測(cè)試了。

沒過多久測(cè)試同學(xué)反饋,你這個(gè)功能咋老白屏?—— 怎么可能?我的代碼不可能有BUG!

來到“事故現(xiàn)場(chǎng)”,稍加思索,打開瀏覽器控制臺(tái),發(fā)現(xiàn)按需加載的遠(yuǎn)程文件下載失敗了。

emmm~,繼續(xù)狡辯,這肯定是公司基建不行啊,網(wǎng)絡(luò)這么不穩(wěn),這鍋我不背!雖然極力狡辯,可是測(cè)試同學(xué)就不相信,就認(rèn)定了是我的問題...

凡事講證據(jù),冷靜下來想一想,萬一真的是我的問題,豈不是很尷尬?

為了挽回局面,于是強(qiáng)裝鎮(zhèn)定說到:“這個(gè)問題是網(wǎng)絡(luò)波動(dòng)導(dǎo)致,雖然咱們的基建環(huán)境不太好,但是為了盡可能提升用戶體驗(yàn),我這嘗試下看看如何優(yōu)化,可通過增加錯(cuò)誤監(jiān)控重試機(jī)制,增強(qiáng)用戶體驗(yàn),追求極致!”,趕緊溜回去看看咋解決吧...

一、Error Boundaries

React官方對(duì)于“Error Boundaries”的介紹:https://reactjs.org/docs/error-boundaries.html

A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an “error boundary”.

簡(jiǎn)單翻譯,在 UI 渲染中發(fā)生的錯(cuò)誤不應(yīng)該阻塞整個(gè)應(yīng)用的運(yùn)行,為此,React 16 中提供了一種新的概念“錯(cuò)誤邊界”。

也就是說,我們可以用“錯(cuò)誤邊界”來優(yōu)雅地處理 React 中的 UI 渲染錯(cuò)誤問題。

React 中的懶加載使用Suspense包裹,其下的子節(jié)點(diǎn)發(fā)生了渲染錯(cuò)誤,也就是下載組件文件失敗,并不會(huì)拋出異常,也沒法兒捕獲錯(cuò)誤,那么用 ErrorBoundary 就正好可以決定再子節(jié)點(diǎn)發(fā)生渲染錯(cuò)誤(常見于白屏)時(shí)候的處理方式。

注意:Error boundaries 不能捕獲如下類型的錯(cuò)誤:

  • 事件處理(了解更多)
  • 異步代碼 (例如 setTimeout 或 requestAnimationFrame 回調(diào))
  • 服務(wù)端渲染
  • 來自ErrorBoundary組件本身的錯(cuò)誤 (而不是來自它包裹子節(jié)點(diǎn)發(fā)生的錯(cuò)誤)

二、借鑒

老夫作為“CV工程師”,自然是信手拈來:

 
 
 
 
  1. class ErrorBoundary extends React.Component { 
  2.   constructor(props) { 
  3.     super(props); 
  4.     this.state = { hasError: false }; 
  5.   } 
  6.  
  7.   static getDerivedStateFromError(error) { 
  8.     // Update state so the next render will show the fallback UI. 
  9.     return { hasError: true }; 
  10.   } 
  11.  
  12.   componentDidCatch(error, errorInfo) { 
  13.     // You can also log the error to an error reporting service 
  14.     logErrorToMyService(error, errorInfo); 
  15.   } 
  16.  
  17.   render() { 
  18.     if (this.state.hasError) { 
  19.       // You can render any custom fallback UI 
  20.       return 

    Something went wrong.

  21.     } 
  22.  
  23.     return this.props.children;  
  24.   } 

使用方法:

 
 
 
 
  1.  
  2.    
  3.  

  • static getDerivedStateFromError(error):在 render phase 階段,子節(jié)點(diǎn)發(fā)生UI渲染拋出錯(cuò)誤時(shí)候執(zhí)行,return 的 {hasError: true} 用于更新 state 中的值,不允許包含副作用的代碼,觸發(fā)重新渲染(渲染fallback UI)內(nèi)容。
  • componentDidCatch(error, errorInfo):在commit phase 階段,捕獲子節(jié)點(diǎn)中發(fā)生的錯(cuò)誤,因此在該方法中可以執(zhí)行有副作用的代碼,例如用于打印上報(bào)錯(cuò)誤日志。

官方案例在線演示地址:https://codepen.io/gaearon/pen/wqvxGa?editors=0010

與此同時(shí)官方的建議:

In the event of an error, you can render a fallback UI with componentDidCatch() by calling setState, but this will be deprecated in a future release. Use static getDerivedStateFromError() to handle fallback rendering instead.

推薦大家在 getDerivedStateFromError() 中處理 fallback UI,而不是在 componentDidCatch() 方法中,componentDidCatch() 在未來的 React 版本中可能會(huì)被廢棄,當(dāng)然只是推薦,僅供參考。

三、修飾

官方的 demo 組件如果要嵌入業(yè)務(wù)代碼中,還是有一些簡(jiǎn)陋,為了更好地適應(yīng)業(yè)務(wù)代碼以及更加通用,我們一步步來改造。

3.1 支持自定義fallback以及error callback

目標(biāo):滿足些場(chǎng)景下,開發(fā)者需要自行設(shè)置 fallback 的UI,以及自定義錯(cuò)誤處理回調(diào)

實(shí)現(xiàn)也非常簡(jiǎn)單,基于 TypeScript,再加上一些類型聲明,一個(gè)支持自定義fallback 和錯(cuò)誤回調(diào)的 ErrorBoundary 就OK了!

 
 
 
 
  1. type IProps = { 
  2.   fallback?: ReactNode | null; 
  3.   onError?: () => void; 
  4.   children: ReactNode; 
  5. }; 
  6.  
  7. type IState = { 
  8.   isShowErrorComponent: boolean; 
  9. }; 
  10.  
  11. class LegoErrorBoundary extends React.Component { 
  12.   static getDerivedStateFromError(error: Error) { 
  13.     return { isShowErrorComponent: true }; 
  14.   } 
  15.  
  16.   constructor(props: IProps | Readonly) { 
  17.     super(props); 
  18.     this.state = { isShowErrorComponent: false }; 
  19.   } 
  20.  
  21.   componentDidCatch(error: Error) { 
  22.     this.props.onError?.(); 
  23.   } 
  24.  
  25.   render() { 
  26.     const { fallback, children } = this.props; 
  27.     if (this.state.isShowErrorComponent) { 
  28.       if (fallback) { 
  29.         return fallback; 
  30.       } 
  31.       return <>加載失敗,請(qǐng)刷新重試!; 
  32.     } 
  33.     return children; 
  34.   } 
  35.  
  36. export default LegoErrorBoundary; 

3.2 支持錯(cuò)誤手動(dòng)重試

我們的按需加載組件就像局部組件更新一樣,當(dāng)組件按需加載的渲染失敗時(shí)候,理論上我們應(yīng)該給用戶提供手動(dòng)/自動(dòng)重試機(jī)制

手動(dòng)重試機(jī)制,簡(jiǎn)單的做法就是,在 fallback UI 中設(shè)置重試按鈕

 
 
 
 
  1.   static getDerivedStateFromError(error: Error) { 
  2.     return { isShowErrorComponent: true }; 
  3.   } 
  4.  
  5.   constructor(props) { 
  6.     super(props); 
  7.     this.state = { isShowErrorComponent: false }; 
  8. +   this.handleRetryClick = this.handleRetryClick.bind(this); 
  9.   } 
  10.      
  11. + handleRetryClick() { 
  12. +  this.setState({ 
  13. +    isShowErrorComponent: false, 
  14. +  }); 
  15. + } 
  16.  
  17.  render() { 
  18.   const { fallback, children } = this.props; 
  19.   if (this.state.isShowErrorComponent) { 
  20.     if (fallback) { 
  21.       return fallback; 
  22.     } 
  23. +    return ( 
  24. +       
     
  25. +        {/* CSS重置下按鈕樣式 */} 
  26. +         
  27. +          渲染錯(cuò)誤,請(qǐng)點(diǎn)擊重試! 
  28. +         
  29. +      
 
  • +    ); 
  •    } 
  •    return children; 
  •  } 
  • 寫一個(gè)普通的Counter(計(jì)數(shù)器)組件:

     
     
     
     
    1. import React, { useState } from 'react'; 
    2.  
    3. const Counter = (props) => { 
    4.     const [count, setCount] = useState(0); 
    5.  
    6.     const handleCounterClick = () => { 
    7.         setCount(count+1); 
    8.     } 
    9.  
    10.     const thr = () => { 
    11.         throw new Error('render error') 
    12.     } 
    13.  
    14.     return ( 
    15.         
       
    16.             {count === 3 ?  thr() : ''} 
    17.             計(jì)數(shù)器:{count} 
    18.             
       
    19.             點(diǎn)擊+1 
    20.         
     
  •     ) 
  •  
  • export default Counter; 
  • 我們使用這個(gè) LegoErrorBoundary 組件包裹 Counter 計(jì)數(shù)器組件,Counter 組件中在第三次點(diǎn)擊時(shí)候拋出一個(gè)異常,來看看 ErrorBoundary 的捕獲處理情況!

    表現(xiàn)效果:

    如果咱不處理這個(gè)錯(cuò)誤,就會(huì)導(dǎo)致“白屏”,也不利于研發(fā)同學(xué)排查問題,特別是涉及到一些異步渲染的問題。

    3.3 支持發(fā)生錯(cuò)誤自動(dòng)重試渲染有限次數(shù)

    手動(dòng)重試,會(huì)增加用戶的一個(gè)操作,這會(huì)增加用戶的操作成本,為了更加便捷用戶使用軟件,提升用戶體驗(yàn),來瞅瞅采用自動(dòng)重試有限次數(shù)的機(jī)制應(yīng)該如何實(shí)現(xiàn)。

    實(shí)現(xiàn)思路:

    重試次數(shù)統(tǒng)計(jì)變量:retryCount,記錄重試渲染次數(shù),超過次數(shù)則使用兜底渲染“錯(cuò)誤提示”UI。

    改造如下:

     
     
     
     
    1. type IState = { 
    2.   isShowErrorComponent: boolean; 
    3. + retryCount: number; 
    4. }; 
    5.  
    6. class LegoErrorBoundary extends React.Component { 
    7. -  static getDerivedStateFromError(error: Error) { 
    8. -    return { isShowErrorComponent: true }; 
    9. -  } 
    10.    
    11.   constructor(props: IProps | Readonly) { 
    12.     super(props); 
    13. +   this.state = { isShowErrorComponent: false, retryCount: 0 }; 
    14. +  this.handleErrorRetryClick = this.handleErrorRetryClick.bind(this); 
    15.   } 
    16.  
    17.   componentDidCatch(error: Error) { 
    18. +  if (this.state.retryCount > 2) { 
    19. +      this.setState({ 
    20. +        isShowErrorComponent: true, 
    21. +      }) 
    22. +    } else { 
    23. +      this.setState({ 
    24. +        retryCount: this.state.retryCount + 1, 
    25. +      }); 
    26. +    } 
    27.   } 
    28.  
    29.   render() { 
    30.     const { fallback, children } = this.props; 
    31.     if (this.state.isShowErrorComponent) { 
    32.       if (fallback) { 
    33.         return fallback; 
    34.       } 
    35. +      return <>重試3次后,展示兜底錯(cuò)誤提示!; 
    36.     } 
    37.     return children; 
    38.   } 
    39.  
    40. export default LegoErrorBoundary; 

    來看看效果:

    自動(dòng)重試3次

    改改Counter組件的代碼,看看能否處理好異步錯(cuò)誤的問題:

     
     
     
     
    1. import React, { useEffect, useState } from 'react'; 
    2.  
    3. const Counter = (props) => { 
    4.   const [count, setCount] = useState(0); 
    5.  
    6.   const handleCounterClick = () => { 
    7.     setCount(count + 1); 
    8.   } 
    9.  
    10.   const thr = () => { 
    11.     throw new Error('render error') 
    12.   } 
    13.  
    14.   useEffect(() => { 
    15.     setTimeout(() => { 
    16.       setCount(3) 
    17.     }, 1000); 
    18.   }, []); 
    19.  
    20.   return ( 
    21.     
       
    22.       { count === 3 ? thr() : '' } 
    23.       計(jì)數(shù)器:{ count } 
    24.        
    25.       點(diǎn)擊+1 
    26.     
     
  •   ) 
  •  
  • export default Counter; 
  • 表現(xiàn):

    處理異步發(fā)生的錯(cuò)誤

    也是OK的!這說明,屬于業(yè)務(wù)邏輯的代碼比如:網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求、異步執(zhí)行導(dǎo)致渲染出錯(cuò)的情況,“錯(cuò)誤邊界”組件都是可以攔截并處理。

    當(dāng)前結(jié)論:使用 Errorboundary 組件包裹,能夠 handle 住子組件發(fā)生的渲染 error。

    四、異步加載組件網(wǎng)絡(luò)錯(cuò)誤

    4.1 嘗試處理

    把 App.js 中的 Counter 組件引用改為按需加載,然后在瀏覽器中模擬分包的組件下載失敗情況,看看是否能夠攔住!

     
     
     
     
    1. const LazyCounter = React.lazy(() => import('./components/counter/index')); 
    2.  
    3. function App() { 
    4.   return ( 
    5.      
    6.        
    7.          
    8.          
    9.            
    10.          
    11.        
    12.     
     
  •   ); 
  • 結(jié)果白屏了!也可以看到 ErrorBoundary 組件中打印了捕獲到的錯(cuò)誤信息:

     
     
     
     
    1. ChunkLoadError: Loading chunk 3 failed. 
    2. (error: http://localhost:5000/static/js/3.18a27ea8.chunk.js) 
    3.     at Function.a.e ((index):1) 
    4.     at App.js:7 
    5.     at T (react.production.min.js:18) 
    6.     at Hu (react-dom.production.min.js:269) 
    7.     at Pi (react-dom.production.min.js:250) 
    8.     at xi (react-dom.production.min.js:250) 
    9.     at _i (react-dom.production.min.js:250) 
    10.     at vi (react-dom.production.min.js:243) 
    11.     at fi (react-dom.production.min.js:237) 
    12.     at Gi (react-dom.production.min.js:285) 

    攔截到了,但是沒有觸發(fā)3次重試,componentDidCatch 中的 console.log('發(fā)生錯(cuò)誤!', error); 只打印了一次錯(cuò)誤日志,就掛了,看到大家的推薦做法是,發(fā)生一次錯(cuò)誤就能夠處理到,所以嘗試把 retryCount 為 0 的時(shí)候就設(shè)置 isShowErrorComponent 的值,

     
     
     
     
    1. this.setState({ 
    2.     isShowErrorComponent: true, 
    3. }) 

    這時(shí)能夠顯示錯(cuò)誤的fallback UI:

    但沒法兒實(shí)現(xiàn)自動(dòng)重試有限次數(shù)異步組件的渲染,否則如果還按照之前的方案,就會(huì)繼續(xù)向上拋出錯(cuò)誤,如果沒有后續(xù) catch 處理錯(cuò)誤,頁面就會(huì)白屏!

    然后嘗試主動(dòng)觸發(fā)重新渲染,發(fā)現(xiàn)并沒有發(fā)起二次請(qǐng)求,點(diǎn)擊重試只是捕獲到了錯(cuò)誤~

    4.2 定位原因

    不生效,于是想到聲明引入組件的代碼如下:

    const LazyCounter = React.lazy(() => import('./components/counter/index'));

    經(jīng)過測(cè)試驗(yàn)證,的確打印了錯(cuò)誤日志,而只發(fā)起了一次網(wǎng)絡(luò)請(qǐng)求的原因是,該 LazyCounter 組件并沒有在組件中聲明,重新渲染的時(shí)候,LazyCounter 組件作為組件外的全局變量,不受 rerender 影響。

    4.3 解決方案

    因此,想要解決網(wǎng)絡(luò)加載錯(cuò)誤問題并重試,就得在聲明代碼 import 的時(shí)候處理,因?yàn)閕mport 返回的是一個(gè)Promise,自然就可以用 .catch 捕獲異常。

     
     
     
     
    1. - const LazyCounter = React.lazy(() => import('./components/counter/index')); 
    2.  
    3. + const LazyCounter = React.lazy(() => import('./components/counter/index').catch(err => { 
    4. +   console.log('dyboy:', err); 
    5. + })); 

    而 import() 代碼執(zhí)行的時(shí)候才會(huì)觸發(fā)網(wǎng)絡(luò)請(qǐng)求拉取分包資源文件,所以我們可以在異常捕獲中重試,并且可以重試一定次數(shù),所以需要實(shí)現(xiàn)一個(gè)工具函數(shù),統(tǒng)一處理 import() 動(dòng)態(tài)引入可能失敗的問題。

    該工具函數(shù)如下:

     
     
     
     
    1. /** 
    2.  *  
    3.  * @param {() => Promise} fn 需要重試執(zhí)行的函數(shù) 
    4.  * @param {number} retriesLeft 剩余重試次數(shù) 
    5.  * @param {number} interval 間隔重試請(qǐng)求時(shí)間,單位ms 
    6.  * @returns Promise 
    7.  */ 
    8. export const retryLoad = (fn, retriesLeft = 5, interval = 1000) => { 
    9.   return new Promise((resolve, reject) => { 
    10.     fn() 
    11.       .then(resolve) 
    12.       .catch(err => { 
    13.         setTimeout(() => { 
    14.           if (retriesLeft === 1) { 
    15.             // 遠(yuǎn)程上報(bào)錯(cuò)誤日志代碼 
    16.             reject(err); 
    17.             // coding... 
    18.             console.log(err) 
    19.             return; 
    20.           } 
    21.           retryLoad(fn, retriesLeft - 1, interval).then(resolve, reject); 
    22.         }, interval); 
    23.       }); 
    24.   }); 

    使用的時(shí)候只需要將 import() 包一下:

     
     
     
     
    1. const LazyCounter = React.lazy(() => retryLoad(import('./components/counter/index'))); 

    與此同時(shí),為了多次請(qǐng)求下,“錯(cuò)誤邊界”組件能夠捕獲到錯(cuò)誤,同時(shí)能夠觸發(fā)兜底渲染邏輯,把 ErrorBoundary 組件發(fā)生錯(cuò)誤時(shí)候直接處理展示兜底邏輯,不做重復(fù)渲染。則將 ErrorBoundary 中的重渲染計(jì)數(shù)邏輯代碼刪除即可。

     
     
     
     
    1. componentDidCatch(error) { 
    2.   console.log('發(fā)生錯(cuò)誤!', error); 
    3.   this.setState({ 
    4.     isShowErrorComponent: true, 
    5.   }); 

    另外,如果我們既想要渲染出錯(cuò)后的重試,還需要保證多次網(wǎng)絡(luò)出錯(cuò)后能有錯(cuò)誤上報(bào),那么只需要在 retryLoad 工具函數(shù)中增加錯(cuò)誤日志遠(yuǎn)程上報(bào)邏輯,然后不拋出 reject()。

    4.4 表現(xiàn)效果

    處理如下三種情況的效果:

    1. 正常按需加載組件成功
    2. 網(wǎng)絡(luò)原因一直下載失敗,展示兜底錯(cuò)誤
    3. 網(wǎng)絡(luò)原因,中途恢復(fù),展示正常功能

    名稱欄目:性能優(yōu)化竟白屏,難道真是我的鍋?
    當(dāng)前路徑:http://m.5511xx.com/article/djiedgo.html