各位读者大家好!Web 开发是一个不断发展的领域。整个生态系统由不同的库和技术组成。React 是 Web 开发中使用最广泛的库之一。
时不时会有小版本发布。然而,今年,React 团队发布了一个重要公告,推出了一个新版本 React 19。2024 年 4 月 25 日,React 正式向公众发布了 React 19 的测试版。
此版本包含大量新功能以及新钩子。在本文中,我们将讨论新版本中的四个新钩子:
-
useFormStatus
-
useActionState
-
useOptimistic
-
use
现有的表单处理实现
在学习第一个钩子之前,让我们看看我们目前如何在 React 中处理表单:
import { submitAction } from "./actions";
const FormHandling = () => {
const [name, setName] = useState("");
const [pending, setPending] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setPending(true);
await submitAction({ name });
setPending(false);
setName("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
placeholder="Enter your name"
onChange={(e) => setName(e.target.value)}
/>
<button type="submit">Submit</button>
{pending && <p>Submitting {name}...</p>}
</form>
);
};
通常,我们使用受控表单来设置表单数据,并通过表单的 onSubmit 属性将表单提交到服务器。此外,我们使用一个 pending 状态变量来处理提交待处理状态。 接下来我将展示的三个钩子将改变我们处理表单的方式。
useFormStatus
useFormStatus 钩子用于在提交表单时提供有关表单状态的信息。该钩子是 React DOM 的一部分,因此需要从 react-dom 导入:
import {useFormStatus} from 'react-dom'
这个钩子的使用方式如下:
const { pending, data } = useFormStatus();
这个钩子不接受任何参数,并返回一个包含有关表单状态的信息的对象:
pending,这是一个布尔值,指示表单是否处于待处理状态。
data是一个FormData类型的对象,包含表单字段的值。
这个 钩子必须在包含
元素的父组件内使用。它只能返回这个父表单元素的状态信息,而不是返回当前组件中渲染的 元素的状态信息。以下操作无效:
const {pending, data} = useFormStatus()
return (
<form action={submit}></form>
);
让我们将上面的表单写在一个单独的组件中:
import { submitAction } from "../../actions";
import { useFormStatus } from "react-dom";
const Form = () => {
const { pending, data } = useFormStatus();
return (
<div>
<input type="text" name="username" placeholder="Enter your name" />
<button disabled={pending} type="submit">
Submit
</button>
{pending && <p>Submitting {data?.get("username")}...</p>}
</div>
);
};
const FormStatusWithHooks = () => {
return (
<form
action={async () => {
await submitAction();
}}
>
<Form />
</form>
);
};
在这个表单中,我们没有使用 onSubmit,而是使用了 form 元素的 action 属性。同时,我们使用了 data 对象来访问表单字段并渲染表单。
你可以在父 元素内使用任意数量的表单,并使用 useFormStatus 钩子获取表单状态。因此,你不需要每次都实现相同的逻辑。这提高了代码的可重用性。
useActionState
useActionState 可以通过以下方式使用:
const [state, formAction] = useActionState(submitData, initialState);
ActionState钩子接受以下参数: submitData,当表单提交时调用的函数。此函数应接受两个参数:当前状态和一个 FormData 对象。initialState,表单未提交时状态的初始值。
useActionState 返回以下内容的数组:
state,正在组件中渲染的当前状态。此状态等于初始状态。
formAction,您可以将其传递给表单元素的 action 属性的新动作。这将执行您使用当前状态传递的动作,并返回一个新的、更新的状态。
我们通过以下表单来了解如何使用这个钩子:
<form action={formAction}>
<div>
<input type="text" name="username" placeholder="Enter your name" />
<input type="number" name="age" placeholder="Enter age" />
<button type="submit">Submit</button>
</div>
</form>
这个表单提交用户信息,姓名和年龄,它会将用户信息附加到存储为状态的用户列表中。让我们使用 useActionState 钩子来获取状态:
import { useActionState, useEffect } from "react";
import { submitActionWithCurrentState } from "../../actions";
const ActionStateComponent = () => {
const [state, formAction] = useActionState(submitActionWithCurrentState, {
users: [],
error: null,
});
return <form action={formAction}>...</form>
submitActionWithCurrentState 方法返回一个修改后的用户列表组成的新状态,并且如果具有给定名称的用户已经存在,则会出现错误。让我们渲染用户列表和错误(如果存在的话):
<div className="error">{state?.error}</div>
{state?.users?.map((user) => (
<div key={user.username}>
Name: {user.username} Age: {user.age}
</div>
))}
在提交表单后,组件会重新渲染并更新组件的状态。
已提交用户列表存储为状态
如果您尝试提交现有用户名,则会收到错误:
useOptimistic
顾名思义,useOptimistic 可以在异步操作仍在进行时优化地更新用户界面。当用户提交表单更改用户界面时,这个钩子可以在表单仍在提交时 “乐观地 ”显示预期结果。
- 如果表单提交成功,用户界面将保持不变。
- 如果表单提交失败,用户界面会恢复到之前的状态。
这样,即使表单提交需要时间,您也可以立即更新用户界面:
const [optimisticState, setOptimisticState] = useOptimistic(actualState, updateFn);
useOptimistic 钩子需要以下参数:
- actualState,实际状态(actualState),即无操作时的乐观状态值。
- updateFn(可选),它是一个函数,用于接收实际状态和传递给 setOptimisticState 方法的值,并计算 optimisticState。如果未指定此参数,则乐观状态等于新值。
返回值如下:
optimisticState,是在操作等待时显示的Optimisti值。setOptimisticState,这是一个将Optimisti状态设置为新值的函数。
我们创建一个表单,执行异步操作来更改页面标题:
import { useOptimistic, useState } from "react";
const OptimisticComponent = () => {
const [title, setTitle] = useState("Title");
const [optimisticTitle, setOptimisticTitle] = useOptimistic(title);
const [error, setError] = useState(null);
const pending = title !== optimisticTitle;
const handleSubmit = async (formData) => {
};
return (
<div>
<h2>{optimisticTitle}</h2>
<p> {pending && "Updating..."} </p>
<form action={handleSubmit}>
<input type="text" name="title" placeholder="Change Title" />
<button type="submit" disabled={pending}>
Submit
</button>
</form>
<div className="error">{error && error}</div>
</div>
);
};
在上面的代码中:
- 我们正在显示标题的optimistic值,当没有任何操作待执行时,该值将被设置为标题状态。
- 我们定义了一个待定变量,如果当前状态和optimistic状态不匹配,该变量将被设置为 true。根据该变量,我们将显示待执行文本并禁用按钮
- 如果异步操作出错,我们会显示一个错误。
现在,我们调用我们的异步函数来解析或拒绝我们的请求:
import { submitTitle } from "../../actions";
const OptimisticComponent = () => {
...
const handleSubmit = async (formData) => {
setError(null);
setOptimisticTitle(formData.get("title"));
try {
const updatedTitle = await submitTitle(formData);
setTitle(updatedTitle);
} catch (e) {
setError(e);
}
};
...
};
在上面的代码中:
- 我们已经按照最初的方法更新了状态。
- 在调用异步操作之前,我们将optimistic状态设置为刚刚提交的更新标题。
- 如果承诺被拒绝,乐观状态就会恢复到原来的状态,因此无需再次设置。
use
use 方法并未作为钩子发布,而是作为 React API 的一部分。与 React 挂钩不同,use 也可以在if和for语句中调用。不过,use 只能在组件或钩子中调用。
通过 use,您可以读取组件中 Promise 或 Context 的值。我们来了解一下它在读取上下文和承诺时的用法。
读取上下
对于上下文,use 的作用与 useContext 钩子类似。它会返回上下文提供的值,供组件使用:
import { createContext, use } from "react";
import "../../styles.css";
const ThemeContext = createContext(null);
const UseHookWithContext = () => {
return (
<ThemeContext.Provider theme="dark">
<MyComponent />
</ThemeContext.Provider>
);
};
const MyComponent = () => {
const theme = use(ThemeContext);
return (
<div className={`myContainer theme-${theme}`}>
<h2>Hi There!</h2>
</div>
);
};
上述代码的输出结果如下:
与 useContext 不同,use 提供了更大的灵活性,因为它也可以使用 if 语句有条件地使用:
const CodeSnippet = ({canShow}) => {
if(canShow) {
const theme = use(ThemeContext)
return <h2>Code snippet shown with {theme} theme </h2>
}
return null
}
读取承诺的解析值
use 可返回承诺的解析值,供组件内部使用。它与暂停应用程序接口(Suspense API)集成,可显示一条临时消息,直到承诺得到解决。
让我们创建一个客户端组件,其中包含一个从服务器组件作为道具传递下来的承诺:
"use client";
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
const DataContainer = ({ dataPromise }) => {
return (
<Suspense fallback={<p>Fetching Data...</p>}>
<DataComponent dataPromise={dataPromise} />
</Suspense>
);
};
const DataComponent = ({ dataPromise }) => {
const data = use(dataPromise);
return <div>{data && data}</div>;
};
在上面的代码中:
- 在服务器组件中创建的承诺会作为道具传递给客户端组件。这反过来又可以传递给use方法。这样,客户端组件就能读取承诺的解析值。
SuspenseAPI 会在承诺挂载时渲染传递给后备props的元素。
点击按钮时显示的数据
如果承诺被拒绝,有两种方法可以向用户显示错误:
-
在 promise 的
catch块中返回错误信息,并将其视为已解决的 promise:export function fetchData() { return new Promise((resolve, reject) => { ... // ... }).catch((err) => err); } -
使用 ErrorBoundary 包裹组件:
拒绝允诺时显示的错误边界
这还不是终点。React 19 还有很多新功能。您可以查看文档中的所有新功能。
目前,React 19 仍处于测试版,因此请勿将其用于生产系统。不过,您可以通过在 package.json 文件中添加以下依赖项并运行 npm i 来安装 React 19 canary 版本:
"dependencies": {
"react": "canary",
"react-dom": "canary",
},
结论
React 团队引入了几个有用的钩子,它们将为开发人员带来更多便利。 前三个钩子:
useFormStatus、useActionState 和 useOptimistic 改变了我们处理表单的方式。使用钩子可以非常方便地获取组件内承诺的解析值。
在这篇文章中,我解释了每个钩子的语法,并演示了它们的使用示例。这一定会帮助你理解这些钩子以及如何使用它们。希望这对你今后的项目有所帮助。
如果您有任何疑问或需要进一步说明,请告诉我。我们非常重视并感谢您的反馈!在 Twitter 上与我联系,了解更多更新和讨论。感谢您的阅读,期待下次再见!