Open kangyana opened 2 years ago
Suspense
使得组件可以“等待”某些操作结束后,再进行渲染。
目前,Suspense
仅支持的使用场景是:通过 React.lazy 动态加载组件。
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
请注意,lazy
组件可以位于 Suspense
组件树的深处——它不必包装树中的每一个延迟加载组件。
最佳实践是将 <Suspense>
置于你想展示加载指示器(loading indicator)的位置,
而 lazy()
则可被放置于任何你想要做代码分割的地方。
在服务端渲染过程中,Suspense
边界允许你挂起,通过较小的块来刷新应用程序。
当组件挂起时,我们会安排一个低优先级的任务来渲染最近的 Suspense
边界的 fallback
。
如果组件在我们刷新 fallback
之前取消挂起,那么我们会发送实际内容并丢弃 fallback
。
Suspense
边界依赖于它们的父边界,在它们可以 hydrate
前被 hydrate
,
但是它们可以独立于兄弟边界进行 hydrate
。
边界 hydrate
前发生的事件将导致边界 hydrate
的优先级高于相邻边界的优先级。
lazy
函数能让你像渲染常规组件一样处理动态引入。
import OtherComponent from './OtherComponent';
// 改为动态引入
const OtherComponent = React.lazy(() => import('./OtherComponent'));
此代码将会在组件首次渲染时,自动导入包含 OtherComponent
组件的包。
lazy
接受一个函数,这个函数需要动态调用 import()
。
它必须返回一个 Promise
,该 Promise
需要 resolve
一个 default export
的 React 组件。
应在 Suspense
组件中渲染 lazy
组件,
如此使得我们可以使用在等待加载 lazy
组件时做优雅降级(如 loading 指示器等)。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
fallback
属性接受任何在组件加载过程中你想展示的 React 元素。
你可以将 Suspense
组件置于懒加载组件之上的任何位置。
你甚至可以用一个 Suspense
组件包裹多个懒加载组件。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
任何组件都可能因渲染而暂停,甚至是已经展示给用户的组件。
为了使屏幕内容始终一致,如果一个已经显示的组件暂停,React 必须隐藏它的树,直到最近的 <Suspense>
边界。
然而,从用户的角度来看,这可能会使人很困惑。
参考这个标签切换的示例:
import React, { Suspense } from 'react';
import Tabs from './Tabs';
import Glimmer from './Glimmer';
const Comments = React.lazy(() => import('./Comments'));
const Photos = React.lazy(() => import('./Photos'));
function MyComponent() {
const [tab, setTab] = React.useState('photos');
function handleTabSelect(tab) {
setTab(tab);
};
return (
<div>
<Tabs onTabSelect={handleTabSelect} />
<Suspense fallback={<Glimmer />}>
{tab === 'photos' ? <Photos /> : <Comments />}
</Suspense>
</div>
);
}
在这个示例中,如果标签从 photos
切换为 comments
,但 Comments
会暂停,用户会看到屏幕闪烁。
这符合常理,因为用户不想看到 photos
,而 Comments
组件还没有准备好渲染其内容,
而 React 为了保证用户体验的一致性,只能显示上面的 Glimmer
,别无选择。
然而,有时这种用户体验并不可取。特别是在准备新 UI 时,展示 “旧” 的 UI 会体验更好。
你可以尝试使用新的 startTransition
API 来让 React 实现这一点:
function handleTabSelect(tab) {
startTransition(() => {
setTab(tab);
});
}
此处代码会告知 React,将标签切换为 comments
不会标记为紧急更新,而是标记为需要一些准备时间的 transition
。
然后 React 会保留旧的 UI 并进行交互,当它准备好时,会切换为 <Comments />
。
如果模块加载失败(如网络问题),它会触发一个错误。
你可以通过 异常捕获边界(Error boundaries)
技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
suspense,lazy