2025-02-23
React
0
请注意,本文编写于 343 天前,最后修改于 161 天前,其中某些信息可能已经过时。

目录

Ract19版本更新指南
Actions
useActionState
form action属性加强
useFormStatus
use
ref
Context 简写
refs 支持清理函数
支持Document Metadata
支持样式表
支持异步脚本
支持预加载资源

Ract19版本更新指南

React在2024年12月正式发布react 19的稳定版,那么19版本react都更新了什么内容呢,接下来小编会以案例的形式带大家了解下这些更新点。

Actions

Actions并不是一个api,而是一个概念词汇。 按照惯例,使用异步过渡的函数被称为 “Actions” Actions 自动为你管理数据提交:

  • 待定状态: Actions 提供一个待定状态,该状态在请求开始时启动,并在最终状态更新提交时自动重置。
  • 乐观更新: Actions 支持新的 useOptimistic Hook,因此你可以在请求提交时向用户显示即时反馈。
  • 错误处理: Actions 提供错误处理,因此当请求失败时,你可以显示错误边界,并自动将乐观更新恢复到其原始值。
  • 表单: 元素现在支持将函数传递给 action 和 formAction 属性。将函数传递给 action 属性默认使用 Actions,并在提交后自动重置表单。

Action必须在startTransition内部使用。

先让我们复习一下startTransition的用法吧。

场景:tab页面切换,Home/Article/About,其中Article渲染页面非常卡顿,当切换到Article时,由页面卡顿,当再切换到其他页面时,会出现切换无效的问题。

tsx
import { memo, useState,useTransition } from "react" interface TabButtonProp { isActive: boolean children: React.ReactNode onClick: () => void } const TabContainer = () => { const [tab, setTab] = useState<"home" | "article" | "about">("home"); const [isPending,startTransition] = useTransition() const selectTab = (newTab: "home" | "article" | "about") => { startTransition(() => { setTab(newTab); }) }; return ( <div> <div> <TabButton isActive={tab === "home"} onClick={() => selectTab("home")}> Home </TabButton> <TabButton isActive={tab === "article"} onClick={() => selectTab("article")} > { isPending ? 'loading...' : 'Article'} </TabButton> <TabButton isActive={tab === "about"} onClick={() => selectTab("about")} > About </TabButton> </div> {tab === "home" && <HomeTab />} {tab === "article" && <ArticleTab />} {tab === "about" && <AboutTab />} </div> ); } const TabButton = ({ children, isActive, onClick }: TabButtonProp) => { const btnStyle = { backgroundColor: isActive ? "#4096ff" : "#fff", border: "1px solid #999", padding: "8px 16px", margin: "0 4px", cursor: "pointer", display: "inline-block", fontSize: "16px", fontWeight: isActive ? "bold" : "normal", color: isActive ? "#fff" : "#000", }; return ( <button style={btnStyle} onClick={onClick}> {children} </button> ); }; const HomeTab = () => { return <h2>Welcome to Home</h2>; } const AboutTab = () => { return <h2>About Us</h2>; } const ArticleTab = () => { return ( <ul> {new Array(100).fill(0).map((item, index) => { return <SlowArticle key={index} index={index} />; })} </ul> ); } // 模拟一个渲染耗时的组件 const SlowArticle = memo(({ index }: {index:number}) => { const startTime = performance.now(); while (performance.now() - startTime < 10) {} // 模拟慢操作 return <li>文章:{index + 1}</li>; }); export default TabContainer;

Actions Demo

tsc
import { useState, useTransition } from "react"; const ActionsDemo = () => { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); const handleSubmit = () => { startTransition(async () => { // react 19 支持异步函数,而在react8中必须是同步函数 const error = await updateName(name); if (error) { setError(error); return; } }); }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); }

useActionState

useActionState 是一个可以根据某个表单动作的结果更新 state 的 Hook。

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

tsx
import { startTransition, useActionState, useState } from "react"; const UseActionStateDemo = () => { const [name, setName] = useState(""); // formData就是调用submitHandle时传递的参数 const action = async (previousState, formData) => { // ajax请求接口更新数据 const resp = await updateName(formData); if (resp.code == 0) { return { success: true, data: resp.data, }; // 无论成功还是失败都必须有返回值,这个返回值会自动更新到state,也作为下一次的previousState } else { return { success: false, message: resp.message, }; } }; const [state, submitHandle, isPending] = useActionState(action, null); return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <button disabled={isPending} onClick={() => { startTransition(() => { // 注意外面必须使用startTransition包裹 submitHandle(name); }); }} > 提交 </button> <h1>{state}</h1> </div> ); }

form action属性加强

tsx
import {useState,useActionState,startTransition} from 'react' function App(){ // formData:FormData const action = async (previousState, formData) => { const resp = await updateName(formData) if(resp.code == 0){ return { success: true, data: resp.data } // 无论成功还是失败都必须有返回值,这个返回值会自动更新到state,也作为下一次的previousState }else{ return { success: false, message: resp.message } } } const [state, submitHandle,isPending] = useActionState(action, null); return ( <div> <form action={submitHandle}> <input name="name1"/> <button disabled={isPending} type="submit">提交</button> </form> <h1>{state}</h1> </div> ) }

useOptimistic 乐观更新

todoList案例

tsx
import {useState,useOptimistic,startTransition} from 'react' const fakeApi = (task) => { return new promise((resolve,reect) => { settimeout(() => { if(Math.random() > 0.5){ resolve(`任务【${task}】添加成功`) }else{ reject('任务添加失败') } },1000) }) } function TaskList(){ const [tasks,setTasks] = useState([]) //待办事项列表 const [optimisticState, addOptimistic] = useOptimistic(state, (currentTasks,newTask) => { return [...currentTasks,newTask] //返回一个新的乐观状态 }); const addTask = (task) => { startTransition(async() => { // 在发起异步任务之前,先更新乐观值,如果请求失败了,会自动回滚乐观状态 addOptimistic(task) // 掉接口, await fakeApi(task) // 更新真实状态 setTasks((currentTask) => { return [...currentTask,task] }) }) } return ( <div> <h1>待办事项列表</h1> <ul> { optimisticState.map((task,index) => (<li key={index}>{task}</li>)) } </ul> </div> ) }

useFormStatus

useFormStatus 读取离它最近的那个 的状态,就像表单是一个 Context 提供者一样

tsx
import {useFormStatus} from 'react-dom'; async function submitForm(){ return new Promise((res) => setTimeout(res,1000)) } function Submit(){ // 获取父组件Form的状态 const { pending, data, method, action } = useFormStatus(); return ( <button type="submit"> {pending ? '提交中...' : '提交'} </button> ) } function Form({action}){ return ( <form action={action}> <input name="message" /> <Submit /> </form> ) } function App(){ return <Form action={submitForm} /> }

use

use 是一个 React API,它可以让你读取类似于 Promise 或 context 的资源的值。

tsx
import { use } from 'react'; function MessageComponent({ messagePromise }) { const message = use(messagePromise); const theme = use(ThemeContext); // ... }

与 React Hook 不同的是,可以在循环和条件语句(如 if)中调用 use。但需要注意的是,调用 use 的函数仍然必须是一个组件或 Hook。

当使用 Promise 调用 use API 时,它会与 Suspense 和 错误边界 集成。当传递给 use 的 Promise 处于 pending 时,调用 use 的组件也会 挂起。如果调用 use 的组件被包装在 Suspense 边界内,将显示后备 UI。一旦 Promise 被解决,Suspense 后备方案将被使用 use API 返回的数据替换。如果传递给 use 的 Promise 被拒绝,将显示最近错误边界的后备 UI。

当 context 被传递给 use 时,它的工作方式类似于useContext。而 useContext 必须在组件的顶层调用,use 可以在条件语句如 if 和循环如 for 内调用。相比之下,use 比 useContext更加灵活。 use 返回传递的 context 的 context 值。React 会搜索组件树并找到 最接近的 context provider 以确定需要返回的 context 值。

ref

在react18中,如果想在父组件中获取子组件的ref,一般通过forwardRef实现。

案例点击按钮让子组件获取焦点

tsx
import { forwardRef, useRef } from "react" function RefDemo() { const inputref = useRef<HTMLInputElement>(null) const focusInput = () => { inputref.current?.focus() } return ( <div> <h1>我是父组件</h1> <MyInput ref={ inputref} /> <button onClick={focusInput}>提交</button> </div> ); } const MyInput = forwardRef<HTMLInputElement,any>((props,ref) => { return <div> <h1>我是子组件</h1> <input ref={ref} type="text"/> </div> }) export default RefDemo

在react19中,ref可以作为props的属性

tsx
import {useRef } from "react" function RefDemo() { const inputref = useRef<HTMLInputElement>(null) const focusInput = () => { console.log(inputref) inputref.current?.focus() } return ( <div> <h1>react19</h1> <MyInput ref={ inputref} /> <button onClick={focusInput}>提交</button> </div> ); } const MyInput = (props) => { return <div> <h1>我是子组件</h1> <input ref={props.ref} type="text"/> </div> } export default RefDemo

Context 简写

React 19 引入了更简洁的 Context 写法,现在可以直接使用 <Context> 代替 <Context.Provider>

tsx
const ThemeContext = createContext(''); function App({children}) { return <ThemeContext value="dark">{children}</ThemeContext>; }

refs 支持清理函数

这将使得在 ref 改变时执行清理操作变得更加容易(允许在组件卸载时自动执行清理逻辑)

场景: 一个按钮可以被另外一个按钮控制显示和隐藏,如果显示动态的添加click事件,隐藏时移除事件

在react8中的做法如下

tsx
import { useEffect, useRef, useState } from "react" function RefsDemo() { const [showBtn, setShowBtn] = useState(true) const btnRef = useRef<HTMLButtonElement>(null) useEffect(() => { if (btnRef.current) { console.log("Button 2 addEventListener"); const handler = () => alert("Button 2 clicked"); btnRef.current.addEventListener("click", handler); return () => { console.log("Button 2 removeEventListener"); btnRef.current?.removeEventListener("click", handler); }; } },[showBtn]) return ( <div> <h1>raect 18</h1> <button onClick={() => setShowBtn(!showBtn)}>点击切换按钮2状态</button> <br></br> { showBtn && <button ref={ btnRef}>按钮2</button> } </div> ); } export default RefsDemo

在react 19版本中的做法如下

tsx
function RefsDemo19() { const [showBtn, setShowBtn] = useState(true); const setRef = (ref) => { if (ref) { console.log("Button 2 addEventListener"); const handler = () => alert("Button 2 clicked"); ref.addEventListener("click", handler); return () => { console.log("Button 2 removeEventListener"); ref?.removeEventListener("click", handler); }; } } return ( <div> <h1>raect 19</h1> <button onClick={() => setShowBtn(!showBtn)}>点击切换按钮2状态</button> <br></br> {showBtn && <button ref={setRef}>按钮2</button>} </div> ); }

支持Document Metadata

tsx
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> ); }

支持样式表

样式表,无论是外部链接的 () 还是内联的 (...),都需要在 DOM 中进行精确的定位,因为样式优先级规则。构建一个允许在组件内部进行组合的样式表功能是困难的,所以用户通常要么将所有的样式远离可能依赖它们的组件加载,要么使用一个封装了这种复杂性的样式库。

在 React 19 中,我们正在解决这个复杂性,并提供更深入的集成到客户端的并发渲染和服务器的流式渲染,内置支持样式表。如果你告诉 React 你的样式表的 precedence,它将管理样式表在 DOM 中的插入顺序,并确保在显示依赖于这些样式规则的内容之前加载样式表(如果是外部的)。

tsx
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> ) }

支持异步脚本

在 HTML 中,普通脚本 (

在 React 19 中,我们通过允许你在组件树的任何位置,即实际依赖脚本的组件内部,渲染它们,从而为异步脚本提供了更好的支持,无需管理脚本实例的重新定位和去重。

tsx
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> }

支持预加载资源

在初始文档加载和客户端更新时,尽早告诉浏览器它可能需要加载的资源,可以显著提高页面性能。

React 19 包含了一些新的 API,用于加载和预加载浏览器资源,使得构建不受资源加载效率影响的优秀体验变得尽可能容易。

tsx
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom' function MyComponent() { preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet prefetchDNS('https://...') // when you may not actually request anything from this host preconnect('https://...') // when you will request something but aren't sure what }

上面的代码渲染的结果如下

html
<!-- the above would result in the following DOM/HTML --> <html> <head> <!-- links/scripts are prioritized by their utility to early loading, not call order --> <link rel="prefetch-dns" href="https://..."> <link rel="preconnect" href="https://..."> <link rel="preload" as="font" href="https://.../path/to/font.woff"> <link rel="preload" as="style" href="https://.../path/to/stylesheet.css"> <script async="" src="https://.../path/to/some/script.js"></script> </head> <body> ... </body> </html>

这些 API 可以通过将像字体这样的额外资源的发现从样式表加载中移出来,优化初始页面加载。它们还可以通过预取预期导航使用的资源列表,然后在点击或甚至悬停时积极预加载这些资源,使客户端更新更快。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:繁星

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!