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官方目前不推薦使用在線上項目中
loading的閃現問題主要是由於api接口時間短,loading不應出現,須要對接口速度進行判斷app
不考慮suspense按照一般的寫法,能夠這麼實現dom
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的觸發時間async
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上海、北京、南京、深圳、杭州,崗位要求可參考https://job.toutiao.com/s/7wokvh
除了前端其餘崗位的也歡迎投遞