本文已收录在 Github: github.com/beichensky/… 中,欢迎 Star,欢迎 Follow!react
本文介绍了 React 18 版本中 Suspense
组件和新增 SuspenseList
组件的使用以及相关属性的用法。而且和 18 以前的版本作了对比,介绍了新特性的一些优点。git
早在 React 16 版本,就可使用 React.lazy
配合 Suspense
来进行代码拆分,咱们来回顾一下以前的用法。github
在编写 User
组件,在 User
组件中进行网络请求,获取数据promise
User.jsx
缓存
import React, { useState, useEffect } from 'react';
// 网络请求,获取 user 数据
const requestUser = id =>
new Promise(resolve =>
setTimeout(() => resolve({ id, name: `用户${id}`, age: 10 + id }), id * 1000)
);
const User = props => {
const [user, setUser] = useState({});
useEffect(() => {
requestUser(props.id).then(res => setUser(res));
}, [props.id]);
return <div>当前用户是: {user.name}</div>;
};
export default User;
复制代码
在 App 组件中经过 React.lazy
的方式加载 User
组件(使用时须要用 Suspense
组件包裹起来哦)markdown
App.jsx
网络
import React from "react";
import ReactDOM from "react-dom";
const User = React.lazy(() => import("./User"));
const App = () => {
return (
<> <React.Suspense fallback={<div>Loading...</div>}> <User id={1} /> </React.Suspense> </>
);
};
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
复制代码
效果图:dom
此时,能够看到 User 组件在加载出来以前会 loading
一下,虽然进行了代码拆分,但仍是有两个美中不足的地方异步
须要在 User
组件中进行一些列的操做:定义 state
,effect
中发请求,而后修改 state
,触发 render
async
虽然看到 loading
展现了出来,可是仅仅只是组件加载完成,内部的请求以及用户想要看到的真实数据尚未处理完成
Ok, 带着这两个问题,咱们继续向下探索。
Suspense
让子组件在渲染以前进行等待,并在等待时显示 fallback 的内容
Suspense
内的组件子树比组件树的其余部分拥有更低的优先级
执行流程
在 render
函数中可使用异步请求数据
react
会从咱们的缓存中读取
若是缓存命中,直接进行 render
若是没有缓存,会抛出一个 promise
异常
当 promise
完成后,react
会从新进行 render
,把数据展现出来
彻底同步写法,没有任何异步 callback
子组件没有加载完成时,会抛出一个 promise
异常
监听 promise
,状态变动后,更新 state
,触发组件更新,从新渲染子组件
展现子组件内容
import React from "react";
class Suspense extends React.Component {
state = {
loading: false,
};
componentDidCatch(error) {
if (error && typeof error.then === "function") {
error.then(() => {
this.setState({ loading: true });
});
this.setState({ loading: false });
}
}
render() {
const { fallback, children } = this.props;
const { loading } = this.state;
return loading ? fallback : children;
}
}
export default Suspense;
复制代码
针对上面咱们说的两个问题,来修改一下咱们的 User
组件
const User = async (props) => {
const user = await requestUser(props.id);
return <div>当前用户是: {user.name}</div>;
};
复制代码
多但愿 User
组件能这样写,省去了不少冗余的代码,而且可以在请求完成以前统一展现 fallback
可是咱们又不能直接使用 async
、await
去编写组件。这时候怎么办呢?
结合上面咱们讲述的 Suspense
实现原理,那咱们能够封装一层 promise
,请求中,咱们将 promise
做为异常抛出,请求完成展现结果。
wrapPromise
函数的含义:
接受一个 promise
做为参数
定义了 promise
状态和结果
返回一个包含 read
方法的对象
调用 read
方法时,会根据 promise
当前的状态去判断抛出异常仍是返回结果。
function wrapPromise(promise) {
let status = "pending";
let result;
let suspender = promise.then(
(r) => {
status = "success";
result = r;
},
(e) => {
status = "error";
result = e;
}
);
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
},
};
}
复制代码
使用 wrapPromise
从新改写一下 User
组件
// 网络请求,获取 user 数据
const requestUser = (id) =>
new Promise((resolve) =>
setTimeout(
() => resolve({ id, name: `用户${id}`, age: 10 + id }),
id * 1000
)
);
const resourceMap = {
1: wrapPromise(requestUser(1)),
};
const User = (props) => {
const resource = resourceMap[props.id];
const user = resource.read();
return <div>当前用户是: {user.name}</div>;
};
复制代码
这时候能够看到界面首先展现 loading
,请求结束后,直接将数据展现出来。不须要编写反作用代码,也不须要在组件内进行 loading
的判断。
上面咱们讲述了 Suspense
的用法,那若是有多个 Suspense
同时存在时,咱们想控制他们的展现顺序以及展现方式,应该怎么作呢?
React 中也提供了一个新的组件:SuspenseList
SuspenseList
组件接受三个属性
revealOrder
: 子 Suspense
的加载顺序
forwards: 从前向后展现,不管请求的速度快慢都会等前面的先展现
Backwards: 从后向前展现,不管请求的速度快慢都会等后面的先展现
together: 全部的 Suspense 都准备好以后同时显示
tail: 指定如何显示 SuspenseList
中未准备好的 Suspense
不设置:默认加载全部 Suspense 对应的 fallback
collapsed:仅展现列表中下一个 Suspense 的 fallback
hidden: 未准备好的项目不限时任何信息
children: 子元素
子元素能够是任意 React 元素
当子元素中包含非 Suspense
组件时,且未设置 tail
属性,那么此时全部的 Suspense
元素一定是同时加载,设置 revealOrder
属性也无效。当设置 tail
属性后,不管是 collapsed
仍是 hidden
,revealOrder
属性便可生效
子元素中多个 Suspense
不会相互阻塞
User
组件
import React from "react";
function wrapPromise(promise) {
let status = "pending";
let result;
let suspender = promise.then(
(r) => {
status = "success";
result = r;
},
(e) => {
status = "error";
result = e;
}
);
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
},
};
}
// 网络请求,获取 user 数据
const requestUser = (id) =>
new Promise((resolve) =>
setTimeout(
() => resolve({ id, name: `用户${id}`, age: 10 + id }),
id * 1000
)
);
const resourceMap = {
1: wrapPromise(requestUser(1)),
3: wrapPromise(requestUser(3)),
5: wrapPromise(requestUser(5)),
};
const User = (props) => {
const resource = resourceMap[props.id];
const user = resource.read();
return <div>当前用户是: {user.name}</div>;
};
export default User;
复制代码
App
组件
import React from "react";
import ReactDOM from "react-dom";
const User = React.lazy(() => import("./User"));
// 此处亦能够不使用 React.lazy(),直接使用如下 import 方式引入也能够
// import User from "./User"
const App = () => {
return (
<React.SuspenseList revealOrder="forwards" tail="collapsed"> <React.Suspense fallback={<div>Loading...</div>}> <User id={1} /> </React.Suspense> <React.Suspense fallback={<div>Loading...</div>}> <User id={3} /> </React.Suspense> <React.Suspense fallback={<div>Loading...</div>}> <User id={5} /> </React.Suspense> </React.SuspenseList>
);
};
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
复制代码
wrapPromise
方法取自 Dan Abramov 的 frosty-hermann-bztrp好了,关于 React 中 Suspense 以及 SuspenseList 组件的用法,就已经介绍完了,在 SuspenseList 使用章节,全部的代码均已贴出来了。有疑惑的地方能够说出来一块儿进行讨论。
文中有写的不对或不严谨的地方,欢迎你们能提出宝贵的意见,十分感谢。
若是喜欢或者有所帮助,欢迎 Star。