淺談React異步組件的使用方法
本篇內容介紹了“React異步組件的使用方法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
創新互聯建站長期為千余家客戶提供的網站建設服務,團隊從業經驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯網生態環境。為吉安企業提供專業的成都網站設計、做網站,吉安網站改版等技術服務。擁有十載豐富建站經驗和眾多成功案例,為您定制開發。
一 前言
今天我們聊一聊React中的異步組件的現況和未來,異步組件很可能是未來從數據交互到UI展示一種流暢的技術方案,所以既然要吃透React,進階React,就有必要搞懂異步組件。
老規矩,我們還是帶著問題開始今天的思考?(自測掌握程度)
1 什么是React異步組件,解決什么問題?
2 componentDidCatch如何捕獲到渲染階段錯誤,又這么彌補。
3 React.lazy如何實現動態加載的?
4 React.lazy為什么要在Supsonse內部。
5 Supsonse原理是什么?
二 初識:異步組件
1 什么是異步組件
我們先來想想目前的React應用中使用ajax或者fetch進行數據交互場景,基本上就是這樣的,在類組件中componentDidMount和函數組件effect中進行數據交互,得到數據后,再渲染UI視圖。那么可不可以讓組件的渲染等待異步數據請求完畢,得到數據后再進行render呢?
對于上面這種情況,第一感覺是難以置信,如果能夠實現讓渲染中斷,等到數據請求之后,再渲染呢?那就是Susponse,上面說到的不可能實現的事,Susponse做到了,React 16.6 新增了,Susponse 讓組件“等待”某個異步操作,直到該異步操作結束即可渲染。
傳統模式:渲染組件-> 請求數據 -> 再渲染組件。
異步模式:請求數據-> 渲染組件。
2 開啟Suspense模式
一個傳統模式下的數據交互應該是這個樣子的。
function Index(){ const [ userInfo , setUserInfo ] = React.useState(0) React.useEffect(()=>{ /* 請求數據交互 */ getUserInfo().then(res=>{ setUserInfo(res) }) },[]) return} export default function Home(){ return{userInfo.name}
;}
流程:頁面初始化掛載,useEffect里面請求數據,通過useState改變數據,二次更新組件渲染數據。
那么如果用Susponse異步模式就可以這么寫:
function FutureAsyncComponent (){ const userInfo = getUserInfo() return} /* 未來的異步模式 */ export default function Home(){ return{userInfo.name}
;} >loading...
當數據還沒有加載完成時候,會展示Suspense中 fallback的內容,彌補請求數據中過渡效果 ,盡管這個模式在現在版本中還不能正式使用,但是將來 React 會支持這樣的代碼形式。
三 溯源:從componentDidCatch到Suspense
至于Suspense是如何將上述不可能的事情變成可能的呢?這就要從 componentDidCatch 說起了,在 React 推出 v16 的時候,就增加了一個新生命周期函數 componentDidCatch。如果某個組件定義了 componentDidCatch,那么這個組件中所有的子組件在渲染過程中拋出異常時,這個 componentDidCatch 函數就會被調用。
componentDidCatch使用
componentDidCatch 可以捕獲異常,它接受兩個參數:
1 error —— 拋出的錯誤。
2 info —— 帶有 componentStack key 的對象,其中包含有關組件引發錯誤的棧信息。
我們來模擬一個子組件渲染失敗的情況:
/* 正常組件,可以渲染 */ function Children(){ returnhello ,let us learn React} /* 非React組件,將無法正常渲染 */ function Children1(){ return } export default class Index extends React.Component{ componentDidCatch(error,info){ console.log(error,info) } render(){ return} }
如上,我們模擬一個render失敗的場景,將一個非React組件Children1當作正常的React的組件來渲染,這樣在渲染階段就會報錯,錯誤信息就會被 componentDidCatch捕獲到,錯誤信息如下:
對于如上如果在渲染子組件的時候出現錯誤,會導致整個組件渲染失敗,無法顯示,正常的組件Children也會被牽連,這個時候我們需要在componentDidCatch做一些補救措施,比如我們發現 componentDidCatch 失敗,可以給Children1加一個狀態控制,如果渲染失敗,那么終止Children1的render。
function ErroMessage(){ return渲染出現錯誤~} export default class Index extends React.Component{ state={ errorRender:false } componentDidCatch(error,info){ /* 補救措施 */ this.setState({ errorRender:true }) } render(){ return} }{ this.state.errorRender ? : }
如果出現錯誤,通過setState重新渲染,并移除失敗的組件,這樣組件就能正常渲染了,同樣也不影響Children掛載。componentDidCatch一方面捕獲在渲染階段出現的錯誤,另一方面可以在生命周期的內部執行副作用去挽回渲染異常帶來的損失。
componentDidCatch原理
componentDidCatch原理應該很好理解,內部可以通過try{}catch(error){}來捕獲渲染錯誤,處理渲染錯誤。
try { //嘗試渲染子組件 } catch (error) { // 出現錯誤,componentDidCatch被調用, }
componentDidCatch思想能否遷移到Suspense上
那么回到我們的異步組件上來,如果讓異步的代碼放在同步執行,是肯定不會正常的渲染的,我們還是要先請求數據,等到數據返回,再用返回的數據進行渲染,那么重點在于這個等字,如何讓同步的渲染停止下來,去等異步的數據請求呢?拋出異常可以嗎? 異常可以讓代碼停止執行,當然也可以讓渲染中止。
Suspense 就是用拋出異常的方式中止的渲染,Suspense 需要一個 createFetcher 函數會封裝異步操作,當嘗試從 createFetcher 返回的結果讀取數據時,有兩種可能:一種是數據已經就緒,那就直接返回結果;還有一種可能是異步操作還沒有結束,數據沒有就緒,這時候 createFetcher 會拋出一個“異常”。
這個“異常”是正常的代碼錯誤嗎?非也,這個異常是封裝請求數據的Promise對象,里面是真正的數據請求方法,既然 Suspense 能夠拋出異常,就能夠通過類似 componentDidCatch的try{}catch{}去獲取這個異常。
獲取這個異常之后干什么呢? 我們知道這個異常是Promise,那么接下來當然是執行這個Promise,在成功狀態后,獲取數據,然后再次渲染組件,此時的渲染就已經讀取到正常的數據,那么可以正常的渲染了。接下來我們模擬一下createFetcher和Suspense
我們模擬一個簡單createFetcher
/** * * @param {*} fn 我們請求數據交互的函數,返回一個數據請求的Promise */ function createFetcher(fn){ const fetcher = { status:'pedding', result:null, p:null } return function (){ const getDataPromise = fn() fetcher.p = getDataPromise getDataPromise.then(result=>{ /* 成功獲取數據 */ fetcher.result = result fetcher.status = 'resolve' }) if(fetcher.status === 'pedding'){ /* 第一次執行中斷渲染,第二次 */ throw fetcher } /* 第二次執行 */ if(fetcher.status) return fetcher.result } }
返回一個函數,在渲染階段執行,第一次組件渲染,由于 status = pedding 所以拋出異常 fetcher 給 Susponse,渲染中止。
Susponse會在內部componentDidCatch處理這個fetcher,執行 getDataPromise.then, 這個時候status已經是resolve狀態,數據也能正常返回了。
接下來Susponse再次渲染組件,此時,此時就能正常的獲取數據了。
我們模擬一個簡單的Suspense
export class Suspense extends React.Component{ state={ isRender: true } componentDidCatch(e){ /* 異步請求中,渲染 fallback */ this.setState({ isRender:false }) const { p } = e Promise.resolve(p).then(()=>{ /* 數據請求后,渲染真實組件 */ this.setState({ isRender:true }) }) } render(){ const { isRender } = this.state const { children , fallback } = this.props return isRender ? children : fallback } }
用 componentDidCatch 捕獲異步請求,如果有異步請求渲染 fallback,等到異步請求執行完畢,渲染真實組件,借此整個異步流程完畢。但為了讓大家明白流程,只是一次模擬異步的過程,實際流程要比這個復雜的多。
流程圖:
四 實踐:從Suspense到React.lazy
React.lazy簡介
Suspense帶來的異步組件的革命還沒有一個實質性的成果,目前版本沒有正式投入使用,但是React.lazy是目前版本Suspense的最佳實踐。我們都知道React.lazy配合Suspense可以實現懶加載,按需加載,這樣很利于代碼分割,不會讓初始化的時候加載大量的文件,減少首屏時間。
React.lazy基本使用
const LazyComponent = React.lazy(()=>import('./text'))
React.lazy接受一個函數,這個函數需要動態調用 import()。它必須返回一個 Promise ,該 Promise需要 resolve 一個 default export 的 React 組件。
我們先來看一下基本使用:
const LazyComponent = React.lazy(() => import('./test.js')) export default function Index(){ returnloading...
我們用Promise模擬一下 import()效果,將如上 LazyComponent改成如下的樣子:
function Test(){ return《React進階實踐指南》即將上線~