ErrorBoundary
React 通过深度优先遍历来调度 Fiber ,所以 ErrorBoundary 组件 有能力捕获到渲染期间子组件发生的异常,并且会阻止异常继续传播,不会导致整个应用程序崩溃。
export default class ErrorBoundary extends React.Component<Props> {
state = { hasError: false, error: null };
static getDerivedStateFromError (error: any) {
return {
hasError: true,
error,
};
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
当子组件发生异常时,会通过调用 getNearestErrorBoundaryID 找到距离发生异常的子组件,最近的 ErrorBoundary 组件,调用 getDerivedStateFromError 方法,将 this.state.hasError 置为 true, 则渲染到 ErrorBoundary 组件时,调用 render 函数,可以利用 hasError 组件状态,在 render 函数中定制子组件发生异常时的 UI。
function getNearestErrorBoundaryID(fiber: Fiber): number | null {
let parent = fiber.return;
while (parent !== null) {
if (isErrorBoundary(parent)) {
return getFiberIDUnsafe(parent);
}
parent = parent.return;
}
return null;
}
Suspense
Suspense 的核心概念与 ErrorBoundary 非常相似,ErrorBoundary 是通过捕获其子组件的异常,来展示错误回滚的 UI。而 Suspense 是通过捕获子组件 抛出的 promise 来展示数据获取之前的 fallBack UI,数据获取之后展示 children UI。
Suspense 解决问题是:极大简化复杂的 stuff, 比如 异步获取数据 (data fetching), 代码分割 (code splitting),以及在你的应用程序当中任何异步依赖的数据。Suspense 可以精准控制异步数据/组件的 Loading 状态。
Suspense 在 异步组件中的使用,就是和 React.lazy 结合,在异步组件获取到之前,显示 fallback UI。
export function lazy<T>(
ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
const payload: Payload<T> = {
// We use these fields to store the result.
_status: Uninitialized,
_result: ctor,
};
}
React.lazy原理相当于就是在组件当中throw了一个thenable函数。
Suspense 在 Loading 中的使用,大家可以去看 React 核心成员 Dan 的 demo。Dan Abramov 的 frosty-hermann-bztrp
Suspense Loading
Suspense 使用
export default class extends Component {
render() {
return (
<Suspense fallback={<h1>加载中</h1>}>
<User />
</Suspense>
</ErrorBoundary>
);
}
}
User 组件在数据获取到之前,即状态为 pending 时会 throw 一个 promise。
// Suspense 参数就是一个发送网络请求的 promise
function wrapperPromise(Suspense: Promise<any>) {
let status = "pending";
let result: any;
return {
read() {
if (status === "success" || status === "error") {
return result;
} else {
// throw 一个 promise
throw promise.then(
(data: any) => {
status = "success";
result = data;
},
(error: any) => {
status = "error";
result = error;
}
);
}
},
};
}
这时, 距离该子组件最近的 Suspense 组件 componentDidCatch 生命周期中,捕获到了该 promise。将状态 loading 置为 true, 调度更新到 Suspense 组件时执行 render 函数时, loading 为 true 渲染 fallBack UI,跳过子组件。
export default class Suspense extends React.Component<
SuspenseProps,
SuspenseState
> {
mounted: any = null;
state = { loading: false };
componentDidCatch(error: any) {
if (typeof error.then === "function") {
this.setState({ loading: true });
error.then(() => {
this.setState({ loading: false });
});
}
}
render() {
const { fallback, children } = this.props;
const { loading } = this.state;
return loading ? fallback : children;
}
}
等到 User 组件在数据获取到之后, 执行 then 回调函数中将状态 loading 置为 false, 再次调度更新时,loaing 为 true 会直接渲染子组件。
componentDidCatch(error: any) {
if (typeof error.then === "function") {
this.setState({ loading: true });
error.then(() => {
this.setState({ loading: false });
});
}
}