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

	// 删除item
  const deleteItem = async (itemId) => {
    // 首先乐观地更新 UI
    addOptimistic(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 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
    }
    

总结

最后,让我用黄玄的一段话作为总结:「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 领域的开源项目开发和知识分享。

欢迎来交个朋友~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

x2600君正 ubi文件系统的编译和烧录

使用平台&#xff1a;君正x2600 ubi文件系统使用问题 1.ubi文件和ubifs文件 2 方法&#xff1a;mkfs.ubifs和ubinize两步打包ubi文件系统 mkfs.ubifs工具 mkfs.ubifs命令用于制作ubifs文件系统&#xff0c;命令示例如下&#xff1a; mkfs.ubifs -x lzo -m 2KiB -e 124KiB -c 3…

FSNotes for Mac v6.7.1中文激活版:强大的笔记管理工具

FSNotes for Mac是一款功能强大的文本处理与笔记管理工具&#xff0c;为Mac用户提供了一个直观、高效的笔记记录和整理平台。 FSNotes for Mac v6.7.1中文激活版下载 FSNotes支持Markdown语法&#xff0c;使用户能够轻松设置笔记格式并添加链接、图像等元素&#xff0c;实现笔记…

【软考高项】第十二章 项目质量管理

目录 12.1管理基础 12.1.1质量与项目质量 12.1.2质量管理 12.1.3质量管理标准体系 12.1.4管理新实践 12.2项目质量管理过程 12.2.1过程概述 12.2.2裁剪考虑因素 12.2.3敏捷与适应方法 12.3规划质量管理 12.3.1输入 12.3.2工具与技术 12.3.3输出 12.4管理质量 12…

最小K个数(力扣面试题17.14)

本文采用的是大堆排序求最小的K个值。需要有堆的数据结构基础哦。 代码展示&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ void AdjustDown(int* parr,int n,int root)//向下调整 {int parentroot;int child parent*21;while…

opencv_23_高斯模糊

void ColorInvert::gaussian_blur(Mat& image) { Mat dst; GaussianBlur(image, dst, Size(0, 0), 15); // Size(2, 2), imshow("图像模糊2", dst); }

代码随想录算法训练营DAY42|C++动态规划Part4|0-1背包理论基础(一)、0-1背包理论基础之滚动数组(二)、416.分割等和子集

文章目录 0-1背包理论基础(一)前置知识01背包动态规划&#xff1a;01背包二维dp数组 CPP代码再论01背包的遍历顺序 0-1背包理论基础(二)一维dp数组如何初始化一维dp数组遍历顺序举例推导dp数组CPP代码 416.分割等和子集思路将题目抽象成0-1背包问题 CPP代码 0-1背包理论基础(一…

2013NOIP普及组真题 4. 车站分级

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1964 核心思想&#xff1a; 1、原文中提到 “如果这趟车次停靠了火车站 x&#xff0c;则始发站、终点站之间所有级别大于等于火车站 x 的都必须停靠”&#xff0c;如果设停靠站为A&…

ansible-playbook离线升级centos内核

目录 概述实践ansible目录结构关键代码执行效果 结束 概述 内核离线包官网下载地址如下&#xff1a; 地址 实践 ansible目录结构 如对 ansible 不熟悉&#xff0c;离线包下载有问题&#xff0c;请至此地址下载&#xff0c;按本文操作可直接使用。 相关文章链接如下 文章地…

如何在iPhone上恢复出厂设置后恢复数据

你不想让这种情况发生&#xff0c;但它确实发生了。您必须将iPhone恢复出厂设置。当您的 iPhone 上出现软件问题且无法修复时&#xff0c;可能会发生这种情况。相反&#xff0c;在更新期间&#xff0c;或者您的iPhone遇到问题时&#xff0c;iPhone上的数据不再存在。 不过不用…

goget配置多个golang 运行环境

一台主机安装多个golang 运行环境 本环境 windows10 为 基础 mac linux也可以按照此方法操作 背景 开发不同的运维工具会用到不同版本的golang&#xff0c;但是开发者不能一直进行重装来处理 &#xff0c;因此 需要一个工具进行golang版本的管理 go管理工具介绍 gvm (Go V…

android webview检测屏幕

1&#xff09;清单文件配置&#xff1a; 配置权限&#xff1a; <uses-permission android:name"android.permission.INTERNET" /> 注册activity&#xff1a; <activityandroid:name".TouchWebViewActivity"android:exported"true"&…

基于随机森林和Xgboost对肥胖风险的多类别预测

基于随机森林和Xgboost对肥胖风险的多类别预测 作者&#xff1a;i阿极 作者简介&#xff1a;数据分析领域优质创作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏…

学习【Mysql运维篇】这一篇就够了

运维篇 1. 日志1-1. 错误日志1-2. 二进制日志1-3. 查询日志1-4. 慢查询日志 2. 主从复制2-1. 概述2-2. 原理2-3. 搭建 3. 分库分表3-1. 介绍3-2. Mycat概述3-3. Mycat入门3-4. Mycat配置3-5. Mycat分片3-6. Mycat管理及监控 4. 读写分类 1. 日志 1-1. 错误日志 错误日志是MyS…

计算机服务器中了mkp勒索病毒怎么办,mkp勒索病毒解密数据恢复流程

网络技术的不断应用与发展&#xff0c;为企业的生产运营带来了极大便利&#xff0c;越来越多的企业依赖网络开展各项工作业务&#xff0c;网络也大大提升了企业的生产运营效率&#xff0c;但网络是一把双刃剑&#xff0c;在为企业提供便利的同时&#xff0c;也为企业的数据安全…

云里物里家电运输新模式:实时定位、智能监控、降本增效

随着电商行业的飞速发展&#xff0c;大家电作为大宗商品&#xff0c;其物流运输过程中面临的痛点日益凸显。如何确保大家电在运输过程中的安全、及时送达以及成本控制&#xff0c;成为了物流企业亟待解决的问题。云里物里自研的物流资产监控管理方案&#xff0c;有效解决了大家…

JAVA面试题分享--集合

常见的数据结构&#xff08;了解&#xff09; 常用的数据结构有&#xff1a;数组&#xff0c;栈&#xff0c;队列&#xff0c;链表&#xff0c;树&#xff0c;散列&#xff0c;堆&#xff0c;图等 数组是最常用的数据结构&#xff0c;数组的特点是长度固定&#xff0c;数组的大…

一、交换网络基础

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

10GMAC层设计系列-(1)10G Ethernet PCS/PMA

一、引言 对于10G以太网MAC层的实现&#xff0c;Xilinx提供了 3种IP核&#xff0c;分别是 10G Ethernet MAC、10G Ethernet PCS/PMA、10G Ethernet Subsystem。 10G Ethernet MAC只包含MAC层&#xff0c;外部需要提供一个PHY芯片进行数据对齐&#xff0c;10G Ethernet MAC与P…

Python 深度学习(二)

原文&#xff1a;zh.annas-archive.org/md5/98cfb0b9095f1cf64732abfaa40d7b3a 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第五章&#xff1a;图像识别 视觉可以说是人类最重要的感官之一。我们依赖视觉来识别食物&#xff0c;逃离危险&#xff0c;认出朋友和家人…

Kompas.ai的可持续内容生态:绿色营销的新选择

在全球环境保护意识日益增强的今天&#xff0c;绿色营销已成为企业树立品牌形象、展示社会责任的重要手段。绿色营销不仅关注产品的环保特性&#xff0c;还包括企业的整体可持续发展战略和对环境的积极贡献。本文将讨论企业如何通过绿色营销树立品牌形象&#xff0c;介绍Kompas…