本文已收錄在 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。