We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
<Suspense />
参考:
本文对于 <Suspense /> 组件的作用,使用场景,实现机制等都不再具体概述,若想要深入了解,请仔细阅读上述参考文章。 本文主要教您如何正确实现一个 <Suspense /> 组件并在项目中成功运行。
Suspense 是 React 提供的一种异步处理的机制。它是React 提供的原生的组件异步调用原语。在 React 没有正确提出 Suspense 这一概念之前,组件请求数据并异步调用通常需要维护一个 loading 变量,来确保组件在不同请求阶段展示不同的视觉效果:
loading
const [loading, setLoading] = useState(true) fetchData( ... ).then( data => { // do something... setLoading(false) }) // switch UI loading ? (<div>Loading ... </div>) : (<other Component />)
而 <Suspense /> 的出现让我们可以用同步的方法去书写异步组件。Suspense 翻译为中文的话是等待、悬垂、悬停的意思。React给出了一个更规范的定义:
Suspense 不是一个‘数据获取库’, 而是一种提供给‘数据获取库’的机制,数据获取库通过这种机制告诉 React 数据还没有准备好,然后 React就会等待它完成后,才继续更新 UI。
也就是说,React 推出了一套专门用于异步组件加载的方式,通过 <Suspense /> 包裹的组件会在数据加载完成前展示预定的组件,等待数据加载完毕后展示完整的组件。
// 异步组件 (包含异步请求) function Posts() { const posts = useQuery(GET_MY_POSTS) return ( <div className="posts"> {posts.map(i => <Post key={i.id} value={i}/>)} </div> ) } function App() { return (<div className="app"> // Suspense 包裹异步组件,并提供一个 fallback 回退组件 <Suspense fallback={<Loader>Posts Loading...</Loader>}> <Posts /> </Suspense> </div>) }
有两个要点值得去注意:
<Suspense /> 其实利用了 React 的 ErrorBoundary 类似的机制来实现。<Suspense /> 可以捕获组件中抛出的 promise 异常,并中断该组件的渲染,并用 fallback 注册的回退组件暂时替代,直到 promise 成功执行或返回非 promise 的异常状态时重启组件渲染。
当组件中抛出 Promise 异常时,React 会向上查找最近的 Suspense 来处理它,如果找不到,React 会抛出错误。
基于这个原理,我们就初步掌握了 <Suspense /> 中断组件渲染的核心 —— 包裹组件抛出 promise 异常即可中断渲染。这个过程是可由我们控制的,通常的场景就是数据请求,当我们挂在组件并请求数据时,可在等待阶段抛出 promise 异常从而调用 fallback 实现等待。 而 <Suspense /> 恢复组件渲染是由异步结果及 <Suspense /> 内部自身掌控和实现的。<Suspense /> 内置了捕获 promise 异常的专属逻辑,它会调用这个 error promise 对象继续执行,等待 promise 状态改变后,主动触发重渲染,代码模拟实现如下:
export interface SuspenseProps { fallback: React.ReactNode } interface SuspenseState { pending: boolean error?: any } export default class Suspense extends React.Component<SuspenseProps, SuspenseState> { // ⚛️ 首先,记录是否处于挂载状态,因为我们不知道异步操作什么时候完成,可能在卸载之后 // 组件卸载后就不能调用 setState 了 private mounted = false // 组件状态 public state: SuspenseState = { // ⚛️ 表示现在正阻塞在异步操作上 pending: false, // ⚛️ 表示异步操作出现了问题 error: undefined } public componentDidMount() { this.mounted = true } public componentWillUnmount() { this.mounted = false } // ⚛️ 使用 Error Boundary 机制捕获下级异常 public componentDidCatch(err: any) { if (!this.mounted) { return } // ⚛️ 判断是否是 Promise, 如果不是则向上抛 if (isPromise(err)) { // 设置为 pending 状态 this.setState({ pending: true }) err.then(() => { // ⚛️ 异步执行成功, 关闭pending 状态, 触发重新渲染 this.setState({ pending: false }) }).catch(err => { // ⚛️ 异步执行失败, 我们需要妥善处理该异常,将它抛给 React // 因为处于异步回调中,在这里抛出异常无法被 React 捕获,所以我们这里先记录下来 this.setState({ error: err || new Error('Suspense Error')}) }) } else { throw err } } // ⚛️ 在这里将 异常 抛给 React public componentDidUpdate() { if (this.state.pending && this.state.error) { throw this.state.error } } public render() { // ⚛️ 在pending 状态时渲染 fallback return this.state.pending ? this.props.fallback : this.props.children } }
👉 注意
setState
createSuspenseResource
我们已知了触发 <Suspense /> 的方法: 异步组件内部 throw Promise。但是仍存在一个问题:异步组件死循环 设想一个场景:
因此,我们需要一个变量来额外缓存当前的 promise 状态,避免死循环的发生,可选方案有:
本文提供的方法是将变量提升到父组件缓存,原理其实一样,实际使用时要学会融会贯通:
type StateType = "initial" | "pending" | "resolved" | "rejected"; const STATE: { [key: string]: StateType } = { INITIAL: "initial", PENDING: "pending", RESOLVED: "resolved", REJECTED: "rejected", }; export interface Resource { read(): any; preload(): void; } export function createSuspenseResource<T>(load: () => Promise<T>): Resource { // 缓存变量 const result: { state: StateType; value: any; } = { state: STATE.INITIAL, value: null, }; // 开启异步操作 function init() { if (result.state !== STATE.INITIAL) { return; } result.state = STATE.PENDING; const p = (result.value = load()); p.then( (res) => { if (result.state === STATE.PENDING) { result.state = STATE.RESOLVED; result.value = res; } }, (err) => { if (result.state === STATE.PENDING) { result.state = STATE.REJECTED; result.value = err; } } ); return p; } return { // 针对不同异步状态调用不同的逻辑 read() { switch (result.state) { case STATE.INITIAL: throw init(); case STATE.PENDING: throw result.value; case STATE.RESOLVED: return result.value; case STATE.REJECTED: throw result.value; } }, preload() { init(); }, }; }
可以看到,该函数的作用无非是两点:
createSuspenseResource 的用法也很简单, 在父组件创建 Resource ,将异步状态缓存变量保存在父组件中,然后将触发对象 resourece 通过 Props 传递给子组件, 子组件调用 resource.read() 触发异步操作,并在每次重渲染时调用 resource.read() 检查当前的异步操作进度。
resourece
resource.read()
组件层级如下
<Parent Component> // create resource const resource = createResource(...) <Suspense> <Child Component resource={resource} > { resourece.read() } </ Child> </ Suspense> </ Parent Component>
The text was updated successfully, but these errors were encountered:
No branches or pull requests
React - Concurrent Mode 之
<Suspense />
参考:
<Suspense />
Suspense 是 React 提供的一种异步处理的机制。它是React 提供的原生的组件异步调用原语。在 React 没有正确提出 Suspense 这一概念之前,组件请求数据并异步调用通常需要维护一个
loading
变量,来确保组件在不同请求阶段展示不同的视觉效果:而
<Suspense />
的出现让我们可以用同步的方法去书写异步组件。Suspense 翻译为中文的话是等待、悬垂、悬停的意思。React给出了一个更规范的定义:也就是说,React 推出了一套专门用于异步组件加载的方式,通过
<Suspense />
包裹的组件会在数据加载完成前展示预定的组件,等待数据加载完毕后展示完整的组件。初窥门径
有两个要点值得去注意:
🔥 原理剖析
<Suspense />
其实利用了 React 的 ErrorBoundary 类似的机制来实现。<Suspense />
可以捕获组件中抛出的 promise 异常,并中断该组件的渲染,并用 fallback 注册的回退组件暂时替代,直到 promise 成功执行或返回非 promise 的异常状态时重启组件渲染。基于这个原理,我们就初步掌握了
<Suspense />
中断组件渲染的核心 —— 包裹组件抛出 promise 异常即可中断渲染。这个过程是可由我们控制的,通常的场景就是数据请求,当我们挂在组件并请求数据时,可在等待阶段抛出 promise 异常从而调用 fallback 实现等待。而
<Suspense />
恢复组件渲染是由异步结果及<Suspense />
内部自身掌控和实现的。<Suspense />
内置了捕获 promise 异常的专属逻辑,它会调用这个 error promise 对象继续执行,等待 promise 状态改变后,主动触发重渲染,代码模拟实现如下:👉 注意
<Suspense />
捕获包裹组件 throw 的 Promise Object 之后,内部调用它并等待 promise 状态改变,主动触发自身的重渲染,其包裹的组件由于是其子组件,也会重启渲染过程,从而以“同步“的姿态获取到请求的数据完成页面布局和绘制。<Suspense />
触发重渲染并不一定是调用setState
,本例只是简单模拟。🌈
createSuspenseResource
我们已知了触发
<Suspense />
的方法: 异步组件内部 throw Promise。但是仍存在一个问题:异步组件死循环设想一个场景:
<Suspense />
包裹组件A 中编写了一个数据请求。因此,我们需要一个变量来额外缓存当前的 promise 状态,避免死循环的发生,可选方案有:
本文提供的方法是将变量提升到父组件缓存,原理其实一样,实际使用时要学会融会贯通:
可以看到,该函数的作用无非是两点:
createSuspenseResource
的用法也很简单, 在父组件创建 Resource ,将异步状态缓存变量保存在父组件中,然后将触发对象resourece
通过 Props 传递给子组件, 子组件调用resource.read()
触发异步操作,并在每次重渲染时调用resource.read()
检查当前的异步操作进度。组件层级如下
The text was updated successfully, but these errors were encountered: