React.suspense是你們用的比較少的功能,它早在2018年的16.6.0版本中就已發佈。它的相關用法有些已經比較成熟,有的相對不太穩定,甚至經歷了重命名、刪除。前端
下面一塊兒來了解下它的主要用法、場景。react
import是webpack中的一種code spliting的用法,可是import的文件返回的是一個promise,必須封裝以後才能使用,例如react-loadable的封裝方法webpack
function Loadable(opts) {
const { loading: LoadingComponent, loader } = opts
return class LoadableComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true, // 是否加載中
loaded: null // 待加載的模塊
}
}
componentDidMount() {
loader()
.then((loaded) => {
this.setState({
loading: false,
loaded
})
})
.catch(() => {})
}
render() {
const { loading, loaded } = this.state
if (loading) {
return <LoadingComponent />
} else if (loaded) {
// 默認加載default組件
const LoadedComponent = loaded.__esModule ? loaded.default : loaded;
return <LoadedComponent {...this.props}/>
} else {
return null;
}
}
}
}
複製代碼
在promise返回後更新組件,若是使用suspense改寫react-loadable,將會更加優雅web
const ProfilePage = React.lazy(() => import('./ProfilePage'));
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
複製代碼
let status = "pending";
let result;
const data = new Promise(resolve => setTimeout(() => resolve("結果"), 1000));
function wrapPromise(promise) {
let suspender = promise.then(
r => {
status = "success";
result = r;
},
e => {
status = "error";
result = e;
}
);
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
}
function App(){
const state = wrapPromise(data);
return (<div>{state}</div>);
}
function Loading(){
return <div>..loading</div>
}
class TodoApp extends React.Component {
render() {
return (
<React.Suspense fallback={<Loading></Loading>}>
<App />
</React.Suspense>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
複製代碼
源碼在此
上面的寫法比較奇怪,在組件App中請求數據state時,一開始返回throw promise,這是爲了讓suspense捕捉到error,返回loading組件,以上寫法跟suspense的實現方式有關api
class Suspense extends React.Component {
state = { promise: null }
componentDidCatch(e) {
if (e instanceof Promise) {
this.setState(
{ promise: e }, () => {
e.then(() => {
this.setState({ promise: null })
})
})
}
}
render() {
const { fallback, children } = this.props
const { promise } = this.state
return <>
{ promise ? fallback : children }
</>
}
}
複製代碼
從suspense源碼能夠看出,suspense捕捉到error後,會對其監聽,當返回值時將loading改成children中的組件。
但這時又會觸發一次組件渲染,因此須要對請求結果緩存,最終變成上面的寫法。
這裏有個官方例子可供參考,傳送門promise
上面的例子很是反人類,在實際項目中基本不可能這樣寫,配合react-cache將會優雅許多緩存
import React, { Suspense } from "react";
import { unstable_createResource as createResource } from "react-cache";
const mockApi = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Hello"), 1000);
});
};
const resource = createResource(mockApi);
const Greeting = () => {
const result = resource.read();
return <div>{result} world</div>;
};
const SuspenseDemo = () => {
return (
<Suspense fallback={<div>loading...</div>}>
<Greeting />
</Suspense>
);
};
export default SuspenseDemo;
複製代碼
react-cache官方目前不推薦使用在線上項目中app
loading的閃現問題主要是由於api接口時間短,loading不應出現,須要對接口速度進行判斷dom
不考慮suspense按照一般的寫法,能夠這麼實現async
const timeout = ms => new Promise((_, r) => setTimeout(r, ms));
const rq = (api, ms, resolve, reject) => async (...args) => {
const request = api(...args);
Promise.race([request, timeout(ms)]).then(resolve, err => {
reject(err);
return request.then(resolve);
});
};
複製代碼
suspense爲咱們提供了maxDuration屬性,用來控制loading的觸發時間
import React from "react";
import ReactDOM from "react-dom";
const {
unstable_ConcurrentMode: ConcurrentMode,
Suspense,
} = React;
const { unstable_createRoot: createRoot } = ReactDOM;
let status = "pending";
let result;
const data = new Promise(resolve => setTimeout(() => resolve("結果"), 3000));
function wrapPromise(promise) {
let suspender = promise.then(
r => {
status = "success";
result = r;
},
e => {
status = "error";
result = e;
}
);
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
}
function Test(){
const state = wrapPromise(data);
return (<div>{state}</div>);
}
function Loading(){
return <div>..loading</div>
}
class TodoApp extends React.Component {
render() {
return (
<Suspense fallback={<Loading></Loading>} maxDuration={500}>
<Test />
</Suspense>
)
}
}
const rootElement = document.getElementById("root");
createRoot(rootElement).render(
<ConcurrentMode>
<TodoApp />
</ConcurrentMode>
);
複製代碼
上面例子使用的是16.8.0版本
例子中用到了unstable_ConcurrentMode、unstable_createRoot語法,unstable_createRoot在16.11.0中已改名爲createRoot,unstable_ConcurrentMode在16.9.0中改名爲unstable_createRoot
在最新16.13.1中測試發現ReactDOM.createRoot並不存在,因此本例子只在16.8.0中測試
以上就是關於suspense的全部場景,目前api善不穩定,謹慎使用
最近字節跳動前端急招,有感興趣的請私信我,或者投遞我郵箱574745389@qq.com
前端base上海、北京、南京、深圳、杭州,崗位要求可參考job.toutiao.com/s/7wokvh
除了前端其餘崗位的也歡迎投遞