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://xiahunao.cn/news/2998486.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

探索银行IT应用系统架构:构建安全高效的金融科技平台

在当今数字化时代&#xff0c;银行业正面临着前所未有的挑战和机遇。作为金融行业的重要组成部分&#xff0c;银行IT应用系统的架构设计至关重要&#xff0c;直接影响着银行的信息化水平、服务效率和安全性。本文将深入探讨典型银行IT应用系统的架构&#xff0c;为银行业构建安…

WordPress缓存插件有哪些?好用的缓存插件分享

目前WordPress缓存插件有&#xff1a;WP Rocket、WP Super Cache、W3 Total Cache、Sucuri、NitroPack、SiteGround Optimizer、LiteSpeed Cache、WP-Optimize、Hummingbird、Cache Enabler、Comet Cache。 在当今的数字世界中&#xff0c;拥有一个高效的网站对于吸引和留住用…

Java全栈开发前端+后端(全栈工程师进阶之路)-环境搭建

在课程开始前我们要配置好我们的开发环境&#xff0c;这里我的电脑太乱了&#xff0c;我使用vm虚拟机进行搭建开发环境&#xff0c;如果有需要环境的或者安装包&#xff0c;可以私信我。 那我们开始 首先我们安装数据库 这里我们使用小皮面板 小皮面板(phpstudy) - 让天下没…

解决RTC内核驱动的问题bm8563

常用pcf-8563 , 国产平替BM8563(驱动管脚一致)&#xff1b; 实时时钟是很常用的一个外设&#xff0c;通过实时时钟我们就可以知道年、月、日和时间等信息。 因此在需要记录时间的场合就需要实时时钟&#xff0c;可以使用专用的实时时钟芯片来完成此功能 RTC 设备驱动是一个标准…

Grafana页面嵌入自建Web应用页面

目录 一、应用场景 二、实现方式 1、修改Grafana配置文件 2、获取监控页面url 3、隐藏左侧和顶部菜单 一、应用场景 需要将Grafana监控页面嵌入自建Web应用页面&#xff0c;使Grafana监控页面成为自建Web应用的一部分。 二、实现方式 总体思路&#xff1a;修改Grafana配…

STM32使用PWM驱动直流电机

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. 直流电机和驱动简介 2. 驱动电路原理 3. 代码实现 3.1 PWM.c 3.2 PWM.h 3.3 MOTOR.c 3.4 MOTOR.h 3.5 main.c 3.6 完整工程文件 PWM和OC输出比较详解&#xff1a; STM32定时器的OC比较和PW…

一、交换网络基础

目录 1.交换机的转发行为 2.数据帧的类型 3.ARP地址解析步骤 Hub&#xff1a;物理层设备 交换机&#xff1a;数据链路层设备 1.交换机的转发行为 泛洪&#xff08;Flooding&#xff09;&#xff08;有可能是单播帧&#xff08;未知单播帧&#xff09;&#xff0c;也有可能是…

iOS ------ Method Swizzling (动态方法交换)

一&#xff0c;Method Swizzling 简介 Method&#xff08;方法&#xff09;对应的是objc_method结构体&#xff1b;而objc_method结构体中包含了SEL method_name(方法名&#xff09;&#xff0c;IMP method_imp&#xff08;方法实现&#xff09; // objc_method 结构体 typed…

第三节课,功能2:开发后端用户的管理接口5min(用户的查询/状态更改)【4】

一、代码任务 【录个屏】 二、写代码 2.1 代码文件位置 2.2 代码如下&#xff1a; 2.3 官方文档&#xff1a; 网址&#xff1a; 逻辑删除 | MyBatis-Plus (baomidou.com) 三、代码有bug&#xff0c;没有鉴权&#xff0c;表里添加一个字段。role 管理员 3.1 判断操作的人&am…

IDEA 创建Servlet-HelloWorldServlet

servlet 1.创建空项目2.配置web项目3.配置Tomcat4.加载Tomcat包5.创建HelloWorldServlet类6.配置web.xml7.运行get与post请求 1.创建空项目 2.配置web项目 3.配置Tomcat 4.加载Tomcat包 5.创建HelloWorldServlet类 public class controller extends HttpServlet {Override//get…

linux学习:线程安全(信号量+互斥锁读写锁+条件变量+可重入函数)

目录 信号量 有名信号量 步骤 api 创建、打开一个POSIX有名信号量 对 POSIX 有名信号量进行 P、V 操作 关闭、删除 POSIX 有名信号量 例子 无名信号量 步骤 api 初始化、销毁 POSIX 无名信号量 互斥锁读写锁 例子 两条线程 使用互斥锁来互斥地访问标准输出 在加锁…

【数据结构】合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 Definition for singly-linked list.struct ListNode {int val;struct ListNode *next;};typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct Lis…

Redis 源码学习记录:字符串

redisObject Redis 中的数据对象 server/redisObject.h 是 Redis 对内部存储的数据定义的抽象类型其定义如下&#xff1a; typedef struct redisObject {unsigned type:4; // 数据类型&#xff0c;字符串&#xff0c;哈希表&#xff0c;列表等等unsigned encoding:4; …

吴恩达2022机器学习专项课程(一)8.1 过拟合

目录 什么是过拟合&#xff1f;如何解决过拟合&#xff1f;什么是泛化&#xff1f;它跟过拟合有什么关系&#xff1f;过拟合案例线性回归线性回归的欠拟合线性回归较好的拟合线性回归的过拟合 逻辑回归逻辑回归的欠拟合逻辑回归的较好的拟合逻辑回归的过拟合 总结 什么是过拟合…

Kubernetes学习笔记06

第十六章、Kubernetes容器交付介绍 如何在k8s集群中部署Java项目 容器交付流程 开发代码阶段 编写代码编写Dockerfile【打镜像做准备】持续交付/集成 代码编译打包制作镜像上传镜像仓库应用部署 环境准备PodServiceIngress运维 监控故障排查应用升级 k8s部署Java项目流程 …

Coursera: An Introduction to American Law 学习笔记 Week 06: Civil Procedure (完结)

An Introduction to American Law Course Certificate Course Introduction 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 06: Civil Pro…

解决Pycharm全局搜索与输入法简繁切换快捷键冲突问题

Pycharm中全局搜索快捷键Ctrl Shift F 如图所示&#xff1a; 微软输入法简繁切换快捷键设置&#xff1a; 解决办法&#xff1a; 关掉输入法的切换功能即可&#xff0c;或者更改简繁切换快捷键&#xff0c;毕竟简繁切换使用频率极低。

java连锁美业收银系统源码-美业SaaS系统【微信小程序端】功能及应用场景介绍

博弈美业管理系统源码 连锁多门店美业收银系统源码 多门店管理 / 会员管理 / 预约管理 / 排班管理 / 商品管理 / 促销活动 PC管理后台、手机APP、iPad APP、微信小程序 &#xff08; 需要系统演示视频可联系观看 &#xff09; ▶ 顾客微信小程序端&#xff1a; 场景名称 场…

【C++】学习笔记——string_1

文章目录 四、模板初阶2. 类模板 五、STL简介1. 什么是STL2. STL的六大组件3. 如何学习STL 六、string类1. string类对象的容量操作 未完待续 四、模板初阶 2. 类模板 函数模板就是&#xff1a;模板 函数&#xff1b;类模板就是&#xff1a;模板 类。和函数模板用法基本相同…

Sarcasm detection论文解析 | CASCADE:在线论坛中的语境讽刺检测

论文地址 论文地址&#xff1a; [1805.06413] CASCADE: Contextual Sarcasm Detection in Online Discussion Forums (arxiv.org)CASCADE: Contextual Sarcasm Detection in Online Discussion Forums - ACL Anthology github地址&#xff1a;declare-lab/CASCADE: This repo c…