「回顾 2022,展望 2023,我正在参与2022 年终总结征文大赛活动」
React.lazy
const SomeComponent = lazy(load)
-
初次渲染时未用到的组件延迟加载
-
lazy()则可被放置于任何你想要做代码分割的地方 -
组件第一次被渲染之前延迟加载组件的代码
-
不要在其他组件 内部 声明
lazy组件 -
load: 一个返回 Promise 或另一个 thenable(具有 then 方法的类 Promise 对象)的函数。
- 第一次渲染返回的组件之前,React 是不会调用 load 函数的
- 首次调用
load后,它将等待其解析,然后将解析值渲染成 React 组件 - 返回的 Promise 和 Promise 的解析值都将被缓存,因此 React 不会多次调用
load函数 - 如果 Promise 被拒绝,则 React 将抛出拒绝原因给最近的错误边界处理
Suspense
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent')
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
子组件没有加载完成使用最近的 fallback 组件(通常是 spinner 或者 skeleton)
使用 Suspense-enabled 才会激活 fallback
注意事项
-
在已经挂起的渲染逻辑首次能够挂载之前, React 不会保留 state
-
如果 Suspense 正在展示内容,但是又被挂起,fallback 组件就会重新出现,除非这个更新是被
startTransitionoruseDeferredValue -
组件挂起隐藏已经展示的内容,组件会执行 layout Effect 的 cleanup,当内容又被展示时,再次触发 Effect,这样 layout Effect 就不会执行这样的逻辑
-
可以和 Streaming Server Rendering、Selective Hydration 使用
Suspense 会等待所有 children 加载完
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';
export default function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<Loading />}>
<Biography artistId={artist.id} />
<Panel>
<Albums artistId={artist.id} />
</Panel>
</Suspense>
</>
);
}
function Loading() {
return <h2>🌀 Loading...</h2>;
}
Suspense 嵌套使用
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';
export default function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<BigSpinner />}>
<Biography artistId={artist.id} />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums artistId={artist.id} />
</Panel>
</Suspense>
</Suspense>
</>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
function AlbumsGlimmer() {
return (
<div className="glimmer-panel">
<div className="glimmer-line" />
<div className="glimmer-line" />
<div className="glimmer-line" />
</div>
);
}
新内容加载时保留旧内容不展示 fallback
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
保留已经展示的内容
import { Suspense, startTransition, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
添加过渡效果
import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';
export default function App() {
return (
<Suspense fallback={<BigSpinner />}>
<Router />
</Suspense>
);
}
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
let content;
if (page === '/') {
content = (
<IndexPage navigate={navigate} />
);
} else if (page === '/the-beatles') {
content = (
<ArtistPage
artist={{
id: 'the-beatles',
name: 'The Beatles',
}}
/>
);
}
return (
<Layout isPending={isPending}>
{content}
</Layout>
);
}
function BigSpinner() {
return <h2>🌀 Loading...</h2>;
}
导航切换时候重置 Sespense 边界
-
使用 transition, 会保留已经存在的内容。但是有这样的场景,如果根据路由不同参数展示不同的内容。
-
使用 key 来进行重置
-
路由设计的时候就会考虑
服务端报错提供只有服务端使用的 fallback
-
当你使用 streaming server rendering APIs 时, 也会用到
Suspense处理错误。 -
如果服务端报错了不会阻止服务端渲染。它会找到最近的
Suspense组件把 fallback 放在生成的 HTML里面。 用户首先会看到 fallback 组件展示的内容。 -
客户端会再次渲染相同的组件。如果客户端出错了就会扔出错误并且展示最近的 error boundary,如果客户端没有报错就不会展示这个错误因为内容展示没有问题。
-
你可以在服务端渲染时抽离一些组件。
服务端上面扔出一个错误,使用
Suspense包裹它们使用 fallback 替换 HTML<Suspense fallback={<Loading />}> <Chat /> </Suspense> function Chat() { if (typeof window === 'undefined') { throw Error('Chat should only render on the client.'); } // ... }