0. 说明
React 19的新功能
在12.5官网更新了react19的最新文档react.dev/blog/2024/1…
React v19现已在npm上可用!在《React 19升级指南》中,我们分享了将您的应用程序升级到React 19的分步说明。在这篇文章中,我们将概述React 19的新功能,以及如何采用它们。
React 19改进了以下几个方面:
- 性能改进:React 19引入了新的性能优化,包括对React组件的优化和对React DOM的优化。
- 错误处理:React 19改进了错误处理机制,包括对错误信息的改进和对错误处理的改进。
- 组件库支持:React 19增加了对组件库的支持,包括对React Hooks的改进和对React Context API的改进。
1. action-React DOM:新增钩子
在React应用程序中,一个常见的用例是执行数据变更并随后更新状态。例如,当用户提交一个表单以更改他们的名字时,你会发起一个API请求,并处理响应。过去需要手动处理发送状态、错误、乐观更新和连续请求。 举个例子:你可以使用useState来处理待定(pending)和错误(error)状态
useState处理pending error
// Before Actions
function UpdateName({}) {
const [name, setName] = useState("");
//-------------新功能useState可以处理error-----
const [error, setError] = (null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
//-------------此行设置error状态-----------
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
useTransition
在React 19中,我们增加了对在转换中使用异步函数来自动处理挂起状态、错误、表单和乐观更新的支持。 例如,可以使用useTransition来处理挂起状态:
// Using pending state from Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
//-------------此行使用新增useTransition处理挂起状态--------------
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);//-------------此行
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
异步转换会马上把 isPending 这个状态设为 true ,然后发出异步请求,等所有转换结束后,再把 isPending 设为 false 。这样一来,就算数据在变,能让当前的 Ul 保持有反应、能互动。
按照常规,用了异步转换的函数叫“动作”(Actions)。Actions 会自动管理提交数据:
等待状态(Pending state):Actions 提供一种从请求一开始就有的等待状态,等最终状态更新提交好了,它会自动重置。
乐观更新(Optimistic updates):Actions 支持新的 useOptimistic 钩子,所以您能在提交请求的时候马上给用户反馈。
错误处理(Optimistic updates):Actions 有错误处理的功能,要是请求失败了,您能显示 Error Boundaries ,还能自动把乐观更新变回原来的值。
表单(Forms):
元素现在支持把函数传给 action 和 formAction props 。把函数传给 action props 时,默认会用 Actions ,提交完了会自动重置表单。
useOptimistic useActionState
在Actions的基础上,React 19引入了useOptimistic来管理乐观更新,并添加了一个新的钩子React.useActionState来处理Actions的常见情况。在react-dom中,我们添加了 Actions来自动管理表单,并使用useFormStatus来支持表单中Actions的常见情况。 在 React 19 中,上述示例可以简化为:
function ChangeName({ name, setName }) {
//-------------此行使用新增useActionState处理Actions的常见情况--------------
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
);
}
useActionState
useActionState 接受一个函数(即“Action”),并返回一个包装了 Action 的函数以供调用。这是因为 Action 可以组合。当包装了 Action 的函数被调用时,useActionState 将返回 Action 的最后执行结果作为数据,以及 Action 的待执行状态作为 pending。
const [error, submitAction, isPending] = useActionState(
async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// You can return any result of the action.
// Here, we return only the error.
return error;
}
// handle success
return null;
},
null,
);
<form
Actions还与React 19的React -dom新功能集成。我们增加了对传递函数作为、和元素的action和formAction属性的支持,以自动提交具有操作的表单:
<form action={actionFunction}>
当操作成功时,React将自动为非受控组件重置表单。如果你需要手动重置,你可以调用新的React DOM API requestFormReset。
有关更多信息,请参阅react-dom文档中的, 和
。useFormStatus
在设计系统中,编写设计组件时通常需要访问当前表单的信息,而不必向下钻取props属性。这可以通过Context实现,但为了简化常见情况,我们新增了一个新的钩子useFormStatus
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useOptimistic
function ChangeName({currentName, onUpdateName}) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName);
onUpdateName(updatedName);
};
return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
use
在React 19中,我们引入了一个新的API来在render中读取资源:use。
例如,你可以使用use读取一个promise, React会暂停直到promise resolve:
Use不支持在render中创建promise。 如果你试图传递一个在render中创建的promise来使用,React将会发出警告: 组件被一个未缓存的promise挂起。目前还不支持在客户端组件或hook中创建promise,除非通过与suspend兼容的库或框架。 为了解决这个问题,你需要从suspense支持缓存promise的库或框架传递一个promise。在未来,我们计划发布一些功能,以便在render中更容易缓存promise。
import {use} from 'react';
function Comments({commentsPromise}) {
// `use` will suspend until the promise resolves.
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// When `use` suspends in Comments,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
Note
2. 新的 React DOM 静态 API-可以引入静态js了
我们在 React DOM/静态 中添加了两个用于静态网站生成的新 API:
- prerender
- prerenderToNodeStream
"prerender" 和 "prerenderToNodeStream" 是一些新的 API,它们改进了 "renderToString",可以在静态 HTML 生成中等待数据加载。这些 API 旨在与 Node.js Stream 和 Web Stream 等流式环境一起使用。例如,在 Web Stream 环境中,可以使用 "prerender" 将 React 树转换为静态 HTML。
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
3. 服务端组件
没有服务器的服务器组件 服务器组件可以在构建时运行以读取文件系统或获取静态内容,因此不需要web服务器。例如,您可能希望从内容管理系统读取静态数据。
如果没有服务端组件,在客户端获取静态数据的效果很常见
// bundle.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
function Page({page}) {
const [content, setContent] = useState('');
// NOTE: loads *after* first page render.
useEffect(() => {
fetch(`/api/content/${page}`).then((data) => {
setContent(data.content);
});
}, [page]);
return <div>{sanitizeHtml(marked(content))}</div>;
}
// api.js
app.get(`/api/content/:page`, async (req, res) => {
const page = req.params.page;
const content = await file.readFile(`${page}.md`);
res.send({content});
});
这种模式意味着用户需要下载并解析额外的75K (gzip压缩)库,并在页面加载后等待第二个请求来获取数据,只是为了渲染在页面的生命周期内不会更改的静态内容。
使用服务器组件,你可以在构建时渲染这些组件:
import marked from 'marked'; // Not included in bundle
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
async function Page({page}) {
// NOTE: loads *during* render, when the app is built.
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
然后,渲染的输出可以被服务器端渲染(SSR)为HTML并上传到CDN。当应用加载时,客户端不会看到原始页面组件,也不会看到渲染markdown的昂贵库。客户端只会看到渲染后的输出:
<div><!-- html for markdown --></div>
4.ref as a prop
ref可以作为props 不再使用forwardRef(未来会移除)
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
5 <Context>
<Context> instead of <Context.Provider>:
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
6 refs 支持Cleanup 功能
ref callbacks支持cleanup
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
};
}}
/>
When the component unmounts, React will call the cleanup function returned from the ref callback. This works for DOM refs, refs to class components, and useImperativeHandle.
7. useDeferredValue initial value
function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
8支持 Document Metadata
function BlogPost({post}) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="https://twitter.com/joshcstory/" />
<meta name="keywords" content={post.keywords} />
<p>
Eee equals em-see-squared...
</p>
</article>
);
}
9支持 stylesheets 样式导入
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
<article class="foo-class bar-class">
{...}
</article>
</Suspense>
)
}
function ComponentTwo() {
return (
<div>
<p>{...}</p>
<link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar
</div>
)
}
10支持异步scripts
function MyComponent() {
return (
<div>
//-----------这行--------------
<script async={true} src="..." />
Hello World
</div>
)
}
function App() {
<html>
<body>
<MyComponent>
...
<MyComponent> // won't lead to duplicate script in the DOM
</body>
</html>
}