前言
如同字面意思,Suspense 讓元件遇到非同步操作時進入“懸停”狀態,等非同步操作有結果時再回歸正常狀態。
非同步操作簡單歸為兩類:
- 非同步載入程式碼
- 非同步載入資料
非同步載入程式碼
非同步載入程式碼就是所謂的 code splitting
,實現起來就像是這樣:
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
複製程式碼
值得一提的是目前版本 (截止至 react@16.8) 還不支援服務端渲染,但還是會在以後的版本上支援的。
非同步載入資料
Suspense 非同步載入資料截止到目前都是不穩定的版本,根據 React 16.x Roadmap,大概到2019年中期釋出穩定版本,但是 React 官方提供了一個獨立的包 react-cache,使用它結合 react@16.6.0
可以讓我們提前感受一下 Suspense 非同步載入資料。
import { unstable_createResource } from 'react-cache';
const getSomething = (something) => new Promise((resolve) => {
setTimeout(() => {
resolve(something);
}, 1000);
})
const resource = unstable_createResource((id) => getSomething(id))
function Demo() {
const data = resource.read(666)
return (
<div>{data}</div>
);
}
複製程式碼
細談 Suspense
在上面的例項中,Suspense 元件傳入了 fallback
屬性,這個屬性用於顯示載入中的頁面,就是俗稱的 loading 咯。
那麼我們在想一個問題,如果一個非同步請求資料的過程非常快,這樣會使得載入中畫面一閃而過,導致閃屏。
Suspense 針對這種情況給出解決方案 maxDuration
屬性:
<Suspense fallback={<Spinner />} maxDuration={500}>
// ...
</Suspense>
複製程式碼
當非同步獲取資料的時間大於 maxDuration
時間時展示 fallback
,否則直接展示資料。
需要注意的是 maxDuration
屬性只有在 Concurrent Mode
下才能生效,在以往的 Sync 模式下 maxDuration
始終為0, 具體使用簡單給出一個例項:
// ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM
.unstable_createRoot(document.getElementById('root'))
.render(
<React.unstable_ConcurrentMode>
<App />
</React.unstable_ConcurrentMode>
);
複製程式碼
原理
Suspense 的實現原理頗有爭議。
當我們在 render 內寫非同步請求資料時會丟擲一個異常,當然它應該是一個 promise
,這個異常會被 Suspense 內一個新的生命週期 ComponentDidCatch
捕獲到,在這個生命週期內 Suspense 將子元件渲染為 loading ,等到非同步請求結束,loading結束,此時又回到了正常的元件。
翻了一下 unstable_createResource
的原始碼,果然 Pending
狀態下會 throw
一個 suspender
物件,這個物件就是一個 promise
。
function unstable_createResource(fetch, maybeHashInput) {
// ...
var resource = {
read: function (input) {
// ...
var key = hashInput(input);
var result = accessResult(resource, fetch, input, key);
switch (result.status) {
case Pending:
{
var suspender = result.value;
throw suspender;
}
case Resolved:
{
var _value = result.value;
return _value;
}
case Rejected:
{
var error = result.value;
throw error;
}
default:
// Should be unreachable
return undefined;
}
},
// ...
};
return resource;
}
複製程式碼
總結
不知道你能不能接受如此 hack 的實現方式,固然缺點是有的,但是它帶來的便利性真的讓我異常期待。
參考: