use 是一个 React Hook,它可以让你读取类似于 Promise 或 context 的资源的值。
与其他 React Hook 不同的是,可以在循环和条件语句(如 if)中调用 use。但需要注意的是,调用 use 的函数仍然必须是一个组件或 Hook。
当使用 Promise 调用 use Hook 时,它会与 Suspense 和 错误边界 集成。当传递给 use 的 Promise 处于 pending 时,调用 use 的组件也会 挂起。如果调用 use 的组件被包装在 Suspense 边界内,将显示后备 UI。一旦 Promise 被解决,Suspense 后备方案将被使用 use Hook 返回的数据替换。如果传递给 use 的 Promise 被拒绝,将显示最近错误边界的后备 UI。
什么是Suspense?
Suspense 是 React 的一个特性,用于处理组件的异步加载。主要用于等待某些异步操作完成,然后再渲染组件。它通常与 React 的 lazy 函数一起使用,以实现按需加载组件。Suspense 组件可以包裹整个组件树或特定的部分,在等待异步操作完成期间显示一个后备 UI。
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
React的lazy函数:
React.lazy 是 React 提供的一个函数,用于实现组件的懒加载(Lazy Loading)。懒加载是一种优化手段,它允许你将组件的加载推迟到组件真正需要渲染的时候。这在大型应用中特别有用,因为它可以减小初始加载的包大小,提高应用的性能。
使用 React.lazy 时,你可以按需加载一个动态引入的组件。这个函数接收一个返回 import() 动态引入语法的函数作为参数,返回一个懒加载后的组件。
LazyComponent 是通过 React.lazy 函数懒加载的。当 MyComponent 渲染时,LazyComponent 的代码不会被立即加载,而是在 MyComponent 实际被渲染到页面上时才会进行加载。
什么是错误边界?
错误边界是一种 React 组件,用于捕获并处理其子组件树中的 JavaScript 错误,防止整个应用崩溃。通过在类组件中实现 componentDidCatch 生命周期方法,或者使用 ErrorBoundary 组件,可以捕获发生在组件树中的错误。
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true });
// 这里可以记录错误信息,发送错误报告等
}
render() {
if (this.state.hasError) {
return <div>Something went wrong!</div>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
{/* 其他组件 */}
</ErrorBoundary>
);
}
在 React 类组件中,生命周期方法按照执行顺序可以分为三个阶段:挂载(Mounting)、更新(Updating)、卸载(Unmounting)。以下是这些阶段中常用的生命周期方法:
挂载阶段 (Mounting):
-
constructor(props):
- 构造函数,在组件被创建时调用,用于初始化状态和绑定事件处理方法。
-
static getDerivedStateFromProps(props, state):
- 在组件实例化或者接收到新的 props 时调用,用于派生状态。
- 代替的是
componentWillReceiveProps,以改善组件的可预测性。getDerivedStateFromProps是一个静态方法,不依赖于实例,并且不能访问实例属性。这种变化的目的是为了推动开发者采用更现代的 React 特性和编程模型,如使用 React Hooks 来代替类组件中的生命周期方法。
-
render():
- 渲染方法,返回组件的 React 元素。
-
componentDidMount():
- 在组件挂载到 DOM 后立即调用,用于执行一次性的操作,如数据获取。
更新阶段 (Updating):
-
static getDerivedStateFromProps(props, state):
- 在组件接收到新的 props 时调用,用于派生状态。
-
shouldComponentUpdate(nextProps, nextState):
- 决定是否要触发组件的重新渲染,用于优化性能。
-
render():
- 渲染方法,返回组件的 React 元素。
-
getSnapshotBeforeUpdate(prevProps, prevState):
- 在最新的渲染输出被提交到 DOM 之前调用,返回的值将作为
componentDidUpdate的第三个参数。
- 在最新的渲染输出被提交到 DOM 之前调用,返回的值将作为
-
componentDidUpdate(prevProps, prevState, snapshot):
- 在组件更新完成后调用,用于执行一些操作,如处理更新后的 DOM。
卸载阶段 (Unmounting):
-
componentWillUnmount():
- 在组件即将卸载时调用,用于清理一些资源,如取消网络请求或清除定时器。
错误处理 (Error Handling):
-
static getDerivedStateFromError(error):
- 当在渲染期间、生命周期方法或子组件构造函数中发生错误时调用,用于捕获并更新状态。
-
componentDidCatch(error, errorInfo):
- 在后代组件抛出错误后调用,用于记录错误信息或发送错误报告。
use(resource)
参数
返回值
use Hook 返回从资源中读取的值,类似于 fullfilled Promise 或 context。
Promise
- 在 服务器组件 中获取数据时,应优先使用
async和await而不是use。async和await会从调用await的点开始渲染,而use会在数据获取到后重新渲染组件。 - 在 服务器组件 中创建 Promise 并将其传递给 客户端组件 优于在客户端组件中创建 Promise。在客户端组件中创建的 Promise 每次渲染都会重新创建。从服务器组件传递到客户端组件的 Promise 在重新渲染时保持稳定。(为什么?)
这涉及到 React 中的服务端渲染(Server-Side Rendering,SSR)和客户端渲染(Client-Side Rendering)的概念。 在 React 中,组件的状态和副作用通常与组件的生命周期相关。当涉及到异步操作,比如使用 Promise,如何处理这些异步操作对于 SSR 和客户端渲染可能会有一些差异。
- 在客户端组件中创建的 Promise 每次渲染都会重新创建:
- 这是因为每次 React 组件渲染时,函数组件内的局部变量都会被重新创建。如果你在组件的渲染函数内创建 Promise,那么每次组件重新渲染时都会生成一个新的 Promise 实例。(在函数组件里,整个函数都是渲染函数;在类组件里,render函数是渲染函数)
- 从服务器组件传递到客户端组件的 Promise 在重新渲染时保持稳定:
- 这意味着在服务端渲染时,Promise 是在服务器上创建的,并且在将 HTML 发送到客户端时,Promise 的状态已经被解决。因此,在客户端接管渲染后,Promise 的状态将保持不变,不会重新创建新的 Promise。 这种稳定性对于 SSR 很重要,因为它确保了客户端接管渲染时的数据和状态的一致性。如果 Promise 涉及到异步数据获取,服务端渲染可以确保将这些数据解析到 HTML 中,而客户端接管后不需要再次发起相同的异步请求。
- 应该在服务器组件还是客户端组件解析 Promise?
在 服务器组件 中使用await会在await执行完成前阻塞渲染。而将 Promise 从服务器组件传递到客户端组件可以防止 Promise 阻塞服务器组件渲染。
处理 rejected Promise
在某些情况下,传递给 use 的 Promise 可能会被拒绝(rejected)。可以通过以下方式处理 rejected Promise:
"use client";
import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
export function MessageContainer({ messagePromise }) {
return (
<ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
);
}
function Message({ messagePromise }) {
const content = use(messagePromise);
return <p>Here is the message: {content}</p>;
}
- 使用
Promise.catch提供替代值。
如果希望在传递给use的 Promise 被拒绝(rejected)时提供替代值,可以使用 Promise 的catch方法。在 Promise 对象上调用catch。catch接受一个参数:一个接受错误消息作为参数的函数。由传递给catch的函数 返回 的任何内容都将视为 Promise 的解决值。
import { Message } from './message.js';
export default function App() {
const messagePromise = new Promise((resolve, reject) => {
reject();
}).catch(() => {
return "no new message found.";
});
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}
context
当 context 被传递给 use 时,它的工作方式类似于useContext。而 useContext 必须在组件的顶层调用,use 可以在条件语句如 if 和循环如 for 内调用。相比之下,use 比 useContext更加灵活。