React正式更新!开始学习React 19!

本文为原创文章,原文链接:J实验室,未经授权请勿转载

今年2月份,React 发布消息确认今年发布 v19 版本,尘封两年的版本号终于要更新了(详情点击:React 19 发布在即,抢先学习一下新特性)。那时候,React 成员 Andrew Clark 明确了新版本将在3月或4月发布。

要不怎么说「DDL是第一生产力」,这不4月底了,新版本就踩点发布了。这次发布的版本号是19.0.0-Beta。

虽然只是 Beta 版,但也够让社区兴奋了:

Dan 说「they did what」

Andrew Clark 说「React 19: Never forwardRef again」

Josh W. Comeau 说「Lots of nice quality-of-life improvements here!」

唯一的遗憾是,经 React 成员 lauren 确认,React Compiler 又跳票了。这个东西原来的名称叫做「React Forget」,真就是 Forget 属性拉满了。

总结一下 19.0.0-Beta 版本的发布的特性就是:

  1. 一个 Actions
  2. 三个新 hook
  3. 一个新 API
  4. ref 和 context 用法更方便
  5. 其他支撑类更新、服务端能力更新

接下来本文一个个介绍。

💡欢迎加入「🌍独立全栈开发交流群」,一起学习交流前端和Node技术

Action

Actions 不是一个 API,是一种简化请求数据处理的方法统称。一个合格的 Actions 要能够简化异步操作,让开发者更专注于业务逻辑而不是状态管理。

让我们通过一个简单的例子来理解Actions的作用。假设我们有一个表单,用户可以通过该表单更新他们的姓名。以前,我们可能会使用useState来手动管理表单状态、错误状态和提交状态,代码可能会看起来像这样:

function UpdateName() {const [name, setName] = useState("");const [error, setError] = useState(null);const [isPending, setIsPending] = useState(false);const handleSubmit = async () => {setIsPending(true);const error = await updateName(name);setIsPending(false);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>);
}

这段代码需要手动处理许多细节。但是,有了 React 19 的 Actions,情况就有所优化,我们可以使用 useTransition hook 来处理表单提交,它会自动处理 pending 状态,让我们的代码更加简洁:

function UpdateName() {const [name, setName] = useState("");const [error, setError] = useState(null);const [isPending, startTransition] = useTransition();const handleSubmit = async () => {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>);
}

handleSubmit 函数中,异步请求的逻辑被放入 startTransition 的回调中。startTransition 被调用时,React 会立即将 isPending 设为 true,表示过渡(请求)正在进行。然后 React 会在后台执行 startTransition 的回调函数,发送异步请求。在请求完成后,React 会自动将 isPending 切换为 false。

我们只要将 isPending 绑定到提交按钮的 disabled 属性,这样在请求进行期间按钮会自动进入禁用状态,避免用户重复提交。

下面总结一下 React 对 Actions 的约定和说明:

  • 命名约定:使用异步过渡的函数可以被称为“Actions”。
  • 挂起状态:Actions 自动管理提交数据的挂起状态。当发起请求时,挂起状态会自动启动,当最终状态更新后,挂起状态就会自动重置。这样可以确保用户在等待数据提交时能够获得反馈,同时在请求完成后清除挂起状态。
  • 乐观更新:Actions 支持乐观更新,即在等待请求提交时就向用户显示正确的提交结果。如果最终请求失败,乐观更新会自动恢复到其原始值。
  • 错误处理:Actions 提供了内置的错误处理功能。当请求失败时,你可以使用错误边界来显示错误信息。
  • 表单支持<form> 元素现在支持将函数传递给 actionformAction 属性。通过将函数传递给 action 属性,可以使用 Actions 来处理表单提交,默认情况下会在提交后自动重置表单。这简化了表单处理的过程,使其更加直观和高效。

三个新 Hook

为什么要先介绍 Actions 呢?因为 React 19 在 Actions 基础上引入了三个新 Hook,每一个都是为了简化开发者操作状态的复杂度。

useOptimistic

useOptimistic 的主要目的是让我们可以在等待异步操作结果的时候,先假设操作成功并更新状态,然后再根据实际结果来确认状态。

它的基本用法如下:

import { useOptimistic } from 'react';function AppContainer() {const [optimisticState, addOptimistic] = useOptimistic(state,// updateFn(currentState, optimisticValue) => {// merge and return new state// with optimistic value});
}

其中:

  • state: 初始状态和没有正在进行的操作时返回的状态。
  • updateFn(currentState, optimisticValue): 一个纯函数,接受当前状态和 addOptimistic 传入的乐观更新值,返回合并后的乐观状态。
  • optimisticState: 乐观状态,如果没有正在进行的操作,则等于 state,否则等于 updateFn 的返回值。
  • addOptimistic: 一个用于触发乐观更新的函数,接受一个任意类型的 optimisticValue 参数,并将其传递给 updateFn

useOptimistic 的使用场景非常广泛,例如:表单提交、点赞、收藏、删除等需要即时反馈的场景均适用。

这是一个删除数据乐观更新的例子:

import React, { useState } from 'react';
import { useOptimistic } from 'react';function AppContainer() {// 默认数据const [state, setState] = useState([{ id: 1, name: 'Item 1' },{ id: 2, name: 'Item 2' },{ id: 3, name: 'Item 3' },]);// 定义更新函数,该函数基于当前状态和乐观值(要删除的条目的ID)来更新状态const updateFn = (currentState, optimisticId) => {return currentState.filter(item => item.id !== optimisticId);};const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);// 删除itemconst deleteItem = async (itemId) => {// 首先乐观地更新 UIaddOptimistic(itemId);// 模拟 API 请求延迟setTimeout(() => {// 假设这里是 API 删除调用,完成后更新实际状态setItems(currentItems => currentItems.filter(item => item.id !== itemId));}, 2000);};return (<div><h1>Optimistically Deleting Items</h1><ul>{optimisticState.map(item => (<li key={item.id}>{item.name} <button onClick={() => deleteItem(item.id)}>Delete</button></li>))}</ul></div>);
}export default AppContainer;

useActionState

useActionState 原名叫做 useFormState,19版本启用新名称,返回参数也发生了变化(奇怪的是,React 还未更新 useActionState 的文档,欺负程序员不看文档吗?🐶)。
在这里插入图片描述

这是 useActionState 的最新基本用法:

const [state, action, pending] = useActionState(fn, initialState, permalink?);

其中返回参数有:

  • state: 表示当前的状态。在第一次渲染时,它等于初始状态 initialState。在执行操作后,它将是最新结果。
  • action: 这是一个函数,用于执行操作。当调用这个函数时,它将触发 fn 函数的执行,并更新状态。
  • pending: 这是新增参数,它是一个布尔值,表示当前是否正在执行操作。如果正在执行操作,则为 true,否则为 false

传入的参数有:

  • fn:这是一个函数,action被调用时会触发,随后返回新的值。
  • initialState:这是初始值,如果没有初值,要设置为null。
  • permalink:这是一个可选的字符串参数,通常与server action一起使用。

下面是 useActionState 与 form action 一起使用的例子,实现了更新名称的功能,如果更新失败,页面上显示 error,如果更新成功,跳转到更新后的页面:

import { useActionState } from 'react';function ChangeName({ name, setName }) {// 使用 useActionState 创建与表单操作相关联的状态const [error, submitAction, isPending] = useActionState(// 第一个参数:表单操作函数async (previousState, formData) => {// 在此定义表单操作的逻辑// 这个函数会在表单提交时被调用// 它接收两个参数:// - previousState: 前一个状态,初始为 null,之后为上一次操作的返回值// - formData: 表单数据对象,可通过 formData.get("name") 获取表单字段的值const error = await updateName(formData.get("name"));// 如果操作中出现了错误,则返回错误信息if (error) {return error;}// 如果操作成功,则执行重定向redirect("/path");}, // 第二个参数:初始状态,这里为 null,因为初始状态并不重要null);// 返回表单及相关的状态和行为return (<form action={submitAction}><input type="text" name="name" /><button type="submit" disabled={isPending}>提交</button>{/* 错误信息 */}{error && <p>{error}</p>}</form>);
}

useFormStatus

useFormStatus 用来获取表单提交的状态信息。它的基本用法如下:

const { pending, data, method, action } = useFormStatus();

其中:

  • pending: 一个布尔值,表示父级 <form> 是否正在提交。如果为 true,表示表单正在提交,否则为 false
  • data: 一个实现了 FormData 接口的对象,包含父级 <form> 正在提交的数据。如果没有正在进行的提交或没有父级 <form>,则为 null
  • method: 一个字符串值,表示父级 <form> 使用的 HTTP 方法,可以是 get 或 post。
  • action: 一个指向传递给父级 <form> 的 action 属性的函数的引用。如果没有父级 <form>,则该属性为 null

例如,在 form action 中,开发者可以通过 useFormStatus 获取表单状态:

import { useFormStatus } from "react-dom";
import action from './actions';function Submit() {const status = useFormStatus();return <button disabled={status.pending}>Submit</button>
}export default function App() {return (<form action={action}><Submit /></form>);
}

这个写法是不是熟悉又陌生?如果你想到了 context,那就对了,你可以理解为 useFormStatus 替代了一部分 context provider 的能力,而且写法比 context 要更加简洁。

使用 useFormStatus 还有两个注意点:

  1. useFormStatus Hook 必须在渲染在 <form> 内部的组件中调用。
  2. useFormStatus 只会返回父级 <form> 的状态信息,而不会返回同一组件或其子组件中任何其他 <form> 的状态信息。

一个新 API——use

以前 use 是被归类到 hook,但是 19 版本的文档把 use 放在 API 文档里面,所以它就成了一个新的 API 啦!

use 用于在组件中读取资源的值,这个资源可以是一个 Promise 或者一个 context。

它的基本用法如下:

const value = use(resource);

在实际代码中可能是这样:

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

use 主要是给 Next.js 这样的上层框架使用的。以 Next.js 为例,如果是在服务端组件中获取数据,更推荐使用 async…await,而不是 use。如果是在客户端组件中获取数据,也推荐在服务端组件里创建 Promise,以 props 传递给客户端组件调用。

use 还可以与 Suspense 边界共用。如果调用 use 的组件被包裹在一个 Suspense 边界内,会显示指定的 fallback。一旦 Promise 被 resolve,Suspense 的 fallback 就会被返回的数据替换。如果传给 use 的 Promise 被 reject,最近的错误边界的 fallback 将会被显示。

ref 和 context 用法简化

如果你只使用 React 客户端的能力,那么这一节介绍的变更会是你最关注的。

ref 抛弃 forwardRef

你还记得被 forwardRef 支配的恐惧吗?从 React 19 开始,我们可以抛弃 forwardRef 了。现在开始,ref 可以当作 prop 进行传递。

举个例子:假设我们有一个函数组件 TextInput,它是一个简单的输入框组件,接受一个 placeholder 属性用于设置输入框的占位符文本。现在,我们希望在父组件中获取到输入框的引用,以便在需要时聚焦到输入框上,代码可以这么写:

import React, { useRef } from 'react';// 定义一个函数组件 TextInput
function TextInput({ placeholder, ref }) {return <input placeholder={placeholder} ref={ref} />;
}// 父组件
function ParentComponent() {// 创建一个 ref 来存储输入框的引用const inputRef = useRef(null);// 在某个事件处理函数中获取输入框的引用并聚焦const focusInput = () => {inputRef.current.focus();};return (<div>{/* 将 inputRef 传递给 TextInput 组件,这样 TextInput 组件内部就可以使用这个 ref 了*/}<TextInput placeholder="Enter your name" ref={inputRef} /><button onClick={focusInput}>Focus Input</button></div>);
}export default ParentComponent;

是不是心智负担比使用 forwardRef 要轻得多?

context 可当作 provider

从在 React 19 开始,开发者可以直接将 <Context> 直接作为 provider,而不是使用 <Context.Provider>

假设我们有一个名为 ThemeContext 的 context,用于管理主题信息。在 React 19 中,我们可以像下面这样使用 <ThemeContext> 作为提供者:

import React, { createContext } from 'react';// 创建一个主题上下文
const ThemeContext = createContext('');// App 组件作为主题提供者
function App({ children }) {return (<ThemeContext value="dark">{children}</ThemeContext>);
}

未来 ThemeContext.Provider 会被弃用并移除。

其他更新

本次发布的新特性还有一些属于支撑类特性和拓展服务端能力的特性,因为纯客户端的 React 开发中使用场景很少,所以不再详细介绍,只简单提炼要点:

  • 服务端组件和 server actions 将成为稳定特性,这两个概念属于熟悉 Next.js/Remix 的人已经烂熟于心,而不用 Next.js/Remix 的人根本用不到。

  • useDeferredValue 增加了第二个参数,可选,用来表示初始值。即现在 useDeferredValue 的用法是这样: const value = useDeferredValue(deferredValue, initialValue?);

  • 支持在 React 代码里编写 document metadata,即在页面组件编写<title> <link><meta> 标签会自动添加应用的 <head> 上面:

    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>);
    }
    
  • 支持在 React 代码里编写 stylesheets,即在页面组件编写 <link rel="stylesheet" href="...">

  • 支持在 React 代码里编写 <script async="" src="...">,最终也会自动添加到 <head> 标签内

  • 支持预加载资源,最终也会自动添加到 <head> 标签内:

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

总结

最后,让我用黄玄的一段话作为总结:「Probably the single most critical principle I’ve learned from React is to be fearless in defining new conceptual abstractions and never compromise on the accuracy and composability of these definitions——我从 React 身上学到的最重要的一条原则可能就是,在定义新的概念抽象时要无所畏惧,绝不要在这些定义的准确性和可组合性上妥协」。

关于我

全栈工程师,Next.js 开源手艺人,AI降临派。

今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。

欢迎来交个朋友~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/830539.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打靶日记:midnight

前置 1. 下载靶机 前往https://www.vulnhub.com/&#xff0c;下载我们想要使用的靶机 本次实战使用的靶机是sunset: midnight 2. 导入VMware 我是用的是VM15&#xff0c;这里我们直接 点击文件-》打开-》选择我们下载完的文件&#xff08;如果是压缩包的话记得解压&#…

陪孩子终身成长

文章目录 自序 你必须成长&#xff0c;才能陪孩子成长1 理解养育的本质第1章 为什么说亲子关系决定孩子的一生亲子关系&#xff0c;决定了我们与世界的关系父母对孩子的影响是最大的所有关系都是原生家庭关系的投射我们的思维模式&#xff0c;由父母决定 第2章 远离劣质亲子…

编译工具各版本与操作系统版本号兼容性冷知识 : JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况

编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况 文章目录 编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号…

codePen按钮样式学习

前言 看到codepen里面有的按钮搞得很炫酷&#xff0c;但其实也不是很难&#xff0c;就学习记录一下 逐渐出现边框 大体上来说就是当鼠标悬浮的时候触发四个transition&#xff0c;用after、before和span的after和before四个伪类做hover出来的边框 <div class"btn bt…

速成python

一个只会c的苦手来总结一下py的语法。没有其他语法基础的不建议看 1. 输入输出 print自带换行&#xff0c;可以写print("Hi", end"")取消换行 a input(你好:) # 默认是str print(type(a)) # 输出a的类型 a int(input()) # 或者a int(a) print(type(…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套 (共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09; 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#x…

前端页面单元测试最佳策略:全面涵盖逻辑、组件、流程、UI及性能优化测试,全面保障软件应用的质量

页面级别的测试要求我们从更宏观的角度审视应用&#xff0c;不仅关注单个组件的正确性&#xff0c;还要确保组件间的协作无误&#xff0c;以及用户在应用中的完整体验。通过集成测试、E2E测试和场景测试&#xff0c;我们可以更全面地覆盖应用的各种使用情况&#xff0c;提高软件…

深入浅出一文图解Vision Mamba(ViM)

文章目录 引言&#xff1a;Mamba第一章&#xff1a;环境安装1.1安装教程1.2问题总结1.3安装总结 第二章&#xff1a;即插即用模块2.1模块一&#xff1a;Mamba Vision代码&#xff1a;models_mamba.py运行结果 2.2模块二&#xff1a;MambaIR代码&#xff1a;MambaIR运行结果 第三…

深入浅出TCP 与 UDP

&#x1f525; 引言 在互联网的广阔天地里&#xff0c;TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;作为传输层的两大支柱&#xff0c;各自承担着不同的使命。下面这篇文章将带你从基础到进阶&#xff0c;全…

什么是 Java 集合,Java 集合有几类?

Java集合&#xff08;Java Collections&#xff09;是Java编程语言中一组用于存储和操作数据的框架。它提供了一种便捷的方式来管理和操作数据集合&#xff0c;无论是数组还是其他数据结构。Java集合框架被设计为通用的、可扩展的&#xff0c;并且具有高性能。它为开发人员提供…

解决Linux CentOS 7安装了vim编辑器却vim编辑器不起作用、无任何反应

文章目录 前言一、解决vim不起作用&#xff08;卸载重新安装&#xff09;1.重新安装vim2.测试vim是否能正常使用 二、解决vim: error while loading shared libraries: /lib64/libgpm.so.2: file too short报错三、解决vim编辑器不能使用方向键和退格键问题 remove vim-common …

线上线下收银一体化,新零售POS系统引领连锁门店数字化转型-亿发

在市场竞争日益激烈的背景下&#xff0c;没有哪个商家能够永远屹立不倒。随着互联网技术的快速发展&#xff0c;传统的线下门店面临着来自电商和新零售的新型挑战。实体零售和传统电商都需要进行变革&#xff0c;都需要实现线上线下的融合。 传统零售在客户消费之后就与商家失…

Java 为什么设计成 “String” 不能用 “==” 比较值?

Java中的String是一种特殊的对象类型&#xff0c;用于表示字符串。在Java中&#xff0c;String对象的创建和比较是一个重要的话题&#xff0c;其中&#xff0c;操作符在比较String对象时有着特殊的行为。为了了解Java为什么设计成String不能用比较值&#xff0c;需要深入探讨Ja…

数据结构八:线性表之循环队列的设计

上篇博客&#xff0c;学习了栈&#xff0c;我们可以知道他也是一种线性表&#xff0c;遵从先进后出的原则&#xff0c;在本节&#xff0c;我们进一步学习另一种线性表—队列。就像饭堂里排队打饭的的队伍&#xff0c;作为一种先进先出的线性表&#xff0c;他又有哪些特别之处呢…

公网ip申请ssl仅260

现在很多网站都已经绑定域名&#xff0c;因此使用的都是域名SSL证书保护网站传输信息安全&#xff0c;而没有绑定域名只有公网IP地址的网站想要保护传输信息安全就要申请IP SSL证书。IP SSL证书也是由正规CA认证机构颁发的数字证书&#xff0c;用来保护用户的隐私以及数据安全&…

FLUKE万用表17B+的电压档最大内阻

项目中遇到一个测量兆欧级别电阻两端电压的问题&#xff0c;发现按照上图中的电路搭建出来的电路测得的电压为8.25V左右&#xff0c;按理说应为9V才对&#xff0c;后来想到万用表测量电压档不同的档位会有不同内阻&#xff0c;测量的电阻应远小于万用表电压档内阻才有效。本次测…

Creo Assembly “Save As“时,为什么关联的Drawing无法Save As

问题描述&#xff1a; Creo Assembly 进行“另存为”&#xff0c;勾选了“Copy Drawings”。但操作结果是&#xff0c;该Assembly相关联的Drawing没有被“另存为”。 原因分析&#xff1a; 查看Workspace&#xff0c;发现该Assembly a.asm相关联的Drawing为b.drw&#xff0…

帕金森患者锻炼的小妙招

亲爱的读者朋友们&#xff0c;大家好&#xff01;在这个阳光明媚的一天&#xff0c;我们要和大家分享一份特别的健康礼赞——专为帕金森患者量身定制的锻炼方案。让我们一起走进帕金森患者的世界&#xff0c;了解他们如何通过科学的锻炼方法&#xff0c;改善身体状况&#xff0…

【前端】表格合并如何实现?

简言 介绍实现表格合并的一种方法。 表格合并 表格合并操作是一个比较复杂的操作&#xff0c;它主要分为以下步骤&#xff1a; 获取选中区域选择合并显示的单元格实现合并操作。 我们就逐一实现这三步&#xff0c;最后实现一个较完整的合并操作。&#xff08;不考虑边界情…

区块链交易所开发

在当今数字化时代&#xff0c;区块链技术以其独特的去中心化、安全性和透明性&#xff0c;正在逐步改变我们的生活。其中&#xff0c;区块链交易所作为连接区块链技术与广大投资者的桥梁&#xff0c;其开发与发展备受关注。本文将从技术进步与市场需求两个维度&#xff0c;探讨…