# 异步渲染
Suspense 是 React 提出的一种同步的代码来实现异步操作的方案。Suspense 让组件‘等待’异步操作,异步请求结束后在进行组件的渲染,即异步渲染。
Suspense 是组件,有一个 fallback 属性,用来代替当 Suspense 处于 loading 状态下渲染的内容,Suspense 的 children 就是异步组件。多个异步组件可以用 Suspense 嵌套使用。
/* 子组件 */
function UserInfo() {
const user = getUserInfo()
return <h1>{user.name}</h1>
}
/* 父组件 */
export default function Index() {
return (
<Suspense fallback={<div>loading...</div>}>
<UserInfo />
</Suspense>
)
}Suspense 包裹异步渲染组件 UserInfo ,当 UserInfo 处于数据加载状态下,展示 Suspense 中 fallback 的内容。
异步渲染相比传统数据交互相比:
- 传统模式:挂载组件 -> 请求数据 -> 再渲染组件
- 异步模式:请求数据 -> 渲染组件
- 异步渲染好处
- 不再需要
componentDidMount或useEffect配合做数据交互,也不会因为数据交互后,改变state而产生的二次更新作用 - 代码更加简洁, 逻辑更加清晰
- 不再需要
# 动态加载(懒加载)
Suspense 配合 React.lazy 可以实现动态加载功能:
React.lazy接受一个函数,这个函数需要动态调用import()- 它必须返回一个
Promise,该Promise需要resolve一个default export的 React 组件
const LazyComponent = React.lazy(() => import("./Component"))基本使用:
代码语言:javascript复制const LazyComponent = React.lazy(() => import("./Component"))
export default function Index() {
return (
<Suspense fallback={<div>loading...</div>}>
<LazyComponent />
</Suspense>
)
}用 React.lazy 动态引入 Component 里面的组件,配合 Suspense 实现动态加载组件效果。这样很利于代码分割,不会让初始化的时候加载大量的文件。
# 实现原理
React.lazy 和 Suspense 实现动态加载原理:
整个 render 过程都是同步执行一气呵成的,但是在 Suspense 异步组件情况下允许调用 Render => 发现异步请求 => 悬停,等待异步请求完毕 => 再次渲染展示数据。
Suspense原理Suspense在执行内部可以通过try{}catch{}方式捕获异常,这个异常通常是一个Promise,可以在这个Promise中进行数据请求工作,Suspense内部会处理这个Promise,Promise结束后,Suspense会再一次重新render把数据渲染出来,达到异步渲染的效果

React.lazy原理lazy内部模拟一个 promiseA 规范场景- 完全可以理解
React.lazy用Promise模拟了一个请求数据的过程,但是请求的结果不是数据,而是一个动态的组件。下一次渲染就直接渲染这个组件,所以是React.lazy利用Suspense接收Promise,执行Promise,然后再渲染这个特性做到动态加载的

# 渲染错误边界
React 组件渲染过程如果有一个环节出现问题,就会导致整个组件渲染失败,那么整个组件的 UI 层都会显示不出来,这样造成的危害是巨大的,如果越靠近 APP 应用的根组件,渲染过程中出现问题造成的影响就越大,有可能直接造成白屏的情况。
代码语言:javascript复制function ErrorTest() {
return
}
function Test() {
return <div>This is Test Component</div>
}
class Index extends React.Component {
componentDidCatch(...arg) {
console.log("componentDidCatch", ...arg)
}
render() {
return (
<div>
<ErrorTest />
<div>Just for test componentDidCatch</div>
<Test />
</div>
)
}
}由于 ErrorTest 不是一个真正的组件但是却用来渲染,结果会造成整个 Index 组件渲染异常,Test 也会受到牵连,UI 都不能正常显示。
为了防止如上的渲染异常情况 React 增加了 componentDidCatch 和 static getDerivedStateFromError() 两个额外的生命周期,去挽救由于渲染阶段出现问题造成 UI 界面无法显示的情况。
# componentDidCatch
componentDidCatch 可以捕获异常,它接受两个参数:
error—— 抛出的错误info—— 带有componentStackkey 的对象,其中包含有关组件引发错误的栈信息
componentDidCatch 中可以再次触发 setState,来降级 UI 渲染,componentDidCatch() 会在 commit 阶段被调用,因此允许执行副作用。
class Index extends React.Component {
state = {
hasError: false,
}
componentDidCatch(...arg) {
/* report error */
uploadErrorLog(...arg)
this.setState({
hasError: true,
})
}
render() {
const { hasError } = this.state
return (
<div>
{hasError ? <div>There is something wrong</div> : <ErrorTest />}
<div>Just for test componentDidCatch</div>
<Test />
</div>
)
}
}# static getDerivedStateFromError
React 更期望用 getDerivedStateFromError 代替 componentDidCatch 用于处理渲染异常的情况。getDerivedStateFromError 是静态方法,内部不能调用 setState。
getDerivedStateFromError 返回的值可以合并到 state,作为渲染使用。
class Index extends React.Component {
state = {
hasError: false,
}
static getDerivedStateFromError() {
return {
hasError: true,
}
}
render() {
const { hasError } = this.state
return (
<div>
{hasError ? <div>There is something wrong</div> : <ErrorTest />}
<div>Just for test componentDidCatch</div>
<Test />
</div>
)
}
}注意事项: 如果存在 getDerivedStateFromError 生命周期钩子,那么将不需要 componentDidCatch 生命周期再降级 UI 。
# key 的合理使用
合理的使用 key 有助于能精准的找到用于新节点复用的老节点。
# 异步组件
实现效果
- 异步请求数据,请求完数据再挂载组件
- 没有加载完数据显示 loading 效果
- 可量化生产
思路
- 可以使用
React.lazy实现动态加载,那么可以先请求数据,然后再加载组件,这时候以props形式将数据传递给目标组件,实现异步效果
实现
代码语言:javascript复制function AsyncComponent(Component, api) {
const AsyncComponentPromise = () =>
new Promise(async (resolve, reject) => {
const data = await api()
resolve({
default: (props) => <Component rdata={data} {...props} />,
})
})
return React.lazy(AsyncComponentPromise)
}- 用
AysncComponent作为一个 HOC 包装组件,接受两个参数,第一个参数为当前组件,第二个参数为请求数据的 api - 声明一个函数给
React.lazy作为回调函数,React.lazy要求这个函数必须是返回一个Promise。在Promise里面通过调用 api 请求数据,然后根据返回来的数据rdata渲染组件,别忘了接受并传递props
使用
代码语言:javascript复制const getData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "cell",
say: "hello",
})
}, 1000)
})
}
function Test({ rdata, age }) {
const { name, say } = rdata
console.log("Test render")
return (
<div>
<div>name: {name}</div>
<div>age: {age}</div>
<div>say: {say}</div>
</div>
)
}
export default class Index extends React.Component {
LazyTest = AsyncComponent(Test, getData)
render() {
const { LazyTest } = this
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyTest age={18} />
</Suspense>
</div>
)
}
}注意点
- 需要约定好接受数据格式
rdata和数据交互形式api - 因为数据本质是用闭包缓存的,所以绑定需要在在组件内部,这样才能保证每次父组件挂载,都会重新请求数据,另外也防止内存泄漏情况发生
- 数据源更新维护困难


