导读定位:这份资料的主题是 React 19 新特性,核心落在 React 19 的 use 异步数据获取 与 Suspense 协作,以及这些变化对前端开发方式的影响。它更适合用来理解 React 生态在数据加载、组件渲染和状态管理上的新思路,而不是只记几个 API 名称。

一、这门课解决什么问题

传统 React 组件处理异步数据时,通常要同时维护数据、错误、加载状态,还要在 effect 里发起请求,再把结果写回组件状态。代码能跑,但组件越来越像数据搬运器:渲染逻辑、加载逻辑、错误处理逻辑混在一起,阅读时需要沿着多个状态字段来回确认。React 19 引入的 use 试图把“等待数据”这件事嵌入组件渲染过程,让组件可以表达“这里需要某个 Promise”,再由 React 负责等待和恢复渲染。

这里真正值得看的不是“use 可以传 Promise”这一行语法,而是它改变后的组件结构。使用 use 时,它接受一个 Promise 作为参数;当 Promise 尚未完成时,组件渲染会被阻塞,页面可以配合 Suspense 展示加载状态。这样一来,组件不再需要自己反复判断 loading,也不再必须用 useEffect 承担数据获取。对前端开发来说,这更像是把数据依赖显式化,让组件边界更接近“需要什么数据,就声明需要什么数据”。

不过,use 并不意味着异步逻辑消失,也不意味着所有页面都能直接照搬。它要求调用位置、组件边界、错误处理和数据缓存策略都重新考虑。尤其是当多个请求相互依赖、请求参数变化频繁,或者需要处理失败回退时,单纯把原来的 useEffect 代码改成 use 可能只是换了写法,并没有解决复杂场景。资料包里的示例正好可以帮助判断:哪些写法适合简单数据流,哪些场景仍需要额外的状态管理或请求控制。

二、适合什么基础

这份资料更适合已经能独立写 React 组件、理解 JSX、props、state,并且知道 useEffect 基本用途的学习者。如果你还分不清组件重新渲染、effect 执行、状态更新和浏览器请求之间的关系,建议先把基础概念补清楚,再进入 React 19 的新特性。因为 use 把异步等待放进了 render,理解它的难点不只是 API,而是理解 React 如何安排渲染、暂停和恢复。

有项目经验会更有帮助,特别是亲手处理过接口加载、表单提交、列表刷新或缓存更新的经历。你可以带着原来的代码来对照:原来的 loading 字段由谁维护,原来的请求在哪个 effect 中发起,原来的错误状态如何处理。这样能看出新写法带来的变化,而不是只记住“use 替代 useEffect”这种过于简单的结论。资料包线索中的示例代码,正是观察这种差异的入口。

如果你是完全的新手,也可以把它当作了解 React 生态演变的资料,但不建议只看不练。先完成一个能展示 loading、成功数据和错误状态的组件,再尝试把其中的异步流程拆开,会更符合这份资料的学习方式。它不是那种只看概念就能马上应用的课程,而是需要结合已有代码进行判断和改写。

三、建议先看哪几块

建议先看 use 的基础语义:它接受 Promise,Promise 未完成时会影响组件渲染,完成后再继续展示结果。这里要重点看“为什么 render 会被阻塞”以及“阻塞期间页面如何呈现”,因为这是理解 Suspense 协作的前提。不要只把 use 当成一个更快的请求写法,它真正改变的是组件表达数据依赖的方式。

接下来看 use 与 Suspense 的组合。资料中的示例把 Suspense 作为加载状态的承载方式,组件在等待 Promise 时由外层 Suspense 接管展示,等数据完成后组件再恢复渲染。这里值得精看的是组件边界:哪个组件等待数据,哪个组件负责展示 loading,哪些内容可以放在 Suspense 内,哪些内容必须保持可见。边界选错,页面可能一直空白,也可能反复闪烁。

然后再看 与旧写法的对照。旧方案通常包括 useState 保存数据和 loading、useEffect 发起异步请求、请求返回后 setState 更新 UI;新方案则减少这些中间状态,让组件更直接地表达数据需求。对照时不要只看代码长短,还要比较异常处理、重复请求、请求取消、参数变化和数据一致性。只有把这些差异一起看,才能判断 React 19 的新写法是否适合你的项目。

四、学完后能独立做什么

学完后,你应该能够独立判断一个组件是否适合使用 use:当数据依赖清晰、组件边界明确、加载状态可以由 Suspense 承接时,可以考虑用 Promise 来表达等待;当请求需要复杂取消、频繁重跑,或者必须保证多个状态严格同步时,则要继续评估旧方案或其他请求管理方式。也就是说,目标不是把所有 useEffect 都删掉,而是知道什么时候可以删、什么时候不该删。

你还能独立重写一个基础的异步组件:把数据请求从组件内部杂乱的 loading 流程中拆出来,让组件只关注渲染结果,让 Suspense 负责等待期间的展示,并在失败时给出清晰反馈。对于列表、详情卡片、配置项或简单数据面板,这种结构通常更容易阅读。遇到多个数据源时,也可以先按组件拆分,再观察每个 Promise 的完成顺序和渲染表现。

更进一步,你可以把资料包中的示例当成模板,做成自己的小练习:先保留原始 useState 和 useEffect 版本,再制作使用 use 的版本,最后比较两种写法在 loading、错误、刷新和数据变化时的表现。学完后的成果不一定是完整项目,但可以是一个能够说明设计取舍的组件样例。这样比单纯收藏资料更有价值,因为你至少知道这套新特性在真实页面中如何落地。

五、资料包怎么拆开配合练习

资料包可以先拆成四个部分:第一部分看 use 的语法和 Promise 行为,重点确认它什么时候阻塞渲染、什么时候恢复渲染;第二部分看 Suspense 的加载展示,观察外层边界如何接管等待状态;第三部分看 旧版异步写法,找出 useState、useEffect 和 setState 各自承担的职责;第四部分看 示例代码的完整流程,从请求发起一直跟踪到 UI 更新。

练习时建议先做“代码对照”,再做“场景改造”。对照阶段可以建立一个最小组件,分别展示请求中、请求成功和请求失败三种状态;改造阶段再加入不同参数、重复点击、刷新数据或嵌套组件,观察使用 use 后页面是否稳定。每次改动都记录一个问题:加载状态由谁控制、Promise 是否被重复创建、错误在哪里捕获、组件为什么重新渲染。这样能把资料里的示例变成自己的判断经验。

最后可以把练习整理成一张“使用边界表”:哪些组件适合 use,哪些场景不适合;哪些 loading 可以交给 Suspense,哪些错误必须单独处理;哪些数据可以直接等待,哪些请求还需要额外控制。这个表格比记住 API 更有用,因为它能帮助你回到项目里做选择。资料包的价值也不只在“React 19 新特性”这个标题,而在于让你亲手验证:新的异步写法到底让组件更清楚,还是只是把复杂度换了一个位置。


bb13515d-eb01-4c99-82e2-b6bc11a8dbef.png

use

1.异步数据获取

通过 use 我们可以在组件 render 执行时进行数据获取。使用 use 时,它接受一个 Promise 作为参数,会在 Promise 状态为非 fullfilled 时阻塞组件 render。通常我们会使用 use 配合 Suspense 来一起使用,从而处理在数据获取时的页面加载状态展示。以往在 use 出现之前,我们需要在组件中进行数据获取通常需要经历以下步骤:

  • 第一先创建 useState 用于存储获取后的数据以及控制 Loading 加载状态。
  • 第二是初始化时在 useEffect 中进行异步数据获取。
  • 第三最后在数据获取返回后调用 setState 更新数据和 UI 展示。
示例:
import { useState, useEffect } from 'react';
function getPerson(): Promise<{ name: string }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'react19之前的写法' });
    }, 1000);
  });
}

const personPromise = getPerson();

function App() {
  // 使用 useState 控制 UI 展示和数据存储
  const [loading, setLoading] = useState(false);
  const [name, setName] = useState<string>('');

  // useEffect 中进行数据获取
  useEffect(() => {
    setLoading(true);
    personPromise.then(({ name }) => {
      // 数据获取成功后调用 setState 更新页面展示
      setName(name);
      setLoading(false);
    });
  }, []);

  return (
    <div>
      <p>Hello:</p>
      {loading ? 'loading' : <div>userName: {name}</div>}
    </div>
  );
}

export default App;
 

在 React 19 新增的 use 这个 Api 后,我们可以使用 use 配合 Suspense 来简化这一过程:

import { use, Suspense } from 'react';

function getPerson(): Promise<{ name: string }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'react19之后' });
    }, 1000);
  });
}

const personPromise = getPerson();

function Person() {
  // use Api 接受传入一个 Promise 作为参数
  const person = use(personPromise);

  return <div>userName: {person.name}</div>;
}

function App() {
  return (
    <div>
      <p>Hello:</p>
      {/* 同时配合 Suspense 实现使用 use 组件的渲染加载态 */}
      <Suspense fallback={<div>Loading...</div>}>
        <Person />
      </Suspense>
    </div>
  );
}

export default App;

 

有条件的读取 React Context( useContext => use(context) )


再来看看 use Api 的另一个用途:有条件的读取 React Context。在 React 19 之前要使用 Context ,只能通过 useContenxt 来使用。由于 React Hook 的特殊性,hook 是无法出现在条件判断语句中。无论之后的条件中是否用得到这部分数据,我们都需要将 useContext 声明在整个组件最顶端。

但在 React19 之后,我们可以通过 use api 来有条件的获取 Context 而不必再局限于传统 hook 的一些限制。

import { use } from 'react';
import ThemeContext from './ThemeContext';

function Heading({ children }) {
  if (children == null) {
    return null;
  }

  // 使用 use APi 有条件的获取 Context
  const theme = use(ThemeContext);
  return <h1 style={{ color: theme.color }}>{children}</h1>;
}

 

useTransition的改动

在 React19 版本之前,我们需要通过一系列的 hook 来手动处理待处理状态、错误、乐观更新和顺序请求等等状态。

比如一个常见提交表单的用例:

import { useState } from 'react';
import {Button,Input} from "@douyinfe/semi-ui"

function UpdateName() {
  const [name, setName] = useState<string>('');
  const [error, setError] = useState<any>(null);
  const [isPending, setIsPending] = useState(false);

  const handleSubmit = async () => {
    setIsPending(true);
    const error = await updateName(name);
    setIsPending(false);
    if (error) {
      setError(error);
      return;
    }
    console.log('表单更新完毕')
  };

  return (
    <div>
      <Input value={name} onChange={(event) => setName(event.target.value)} />
      <Button onClick={handleSubmit} disabled={isPending}>
        Update
      </Button>
      {error && <p>{error}</p>}
    </div>
  );
}

 

而在 React19 中,对于 useTransition 提供了异步函数的支持,从而可以使用 useTransition 更加便捷的进行异步的数据处理:

import { useState, useTransition } from 'react';
import {Button,Input} from "@douyinfe/semi-ui"

function updateName(name) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(undefined);
    }, 1000);
  });
}

export default function UpdateName() {
  const [name, setName] = useState('');
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () => {
    // startTransition 中的异步函数被称为 Action
    // 当 startTransition 被调用时 React 会自动变更 isPending 为 true
    // 同理,当函数执行完毕后 isPending 会自动变更为 false
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      }
      console.log('表单更新完毕')
    });
  };

  return (
    <div>
      <Input value={name} onChange={(event) => setName(event.target.value)} />
      <Button onClick={handleSubmit} disabled={isPending}>
        Update
      </Button>
      {error && <p>{error}</p>}
    </div>
  );
}

 


可以看到在 useTransition 返回的 startTransition 函数中,异步的 startTransition 在点击 update 时会将 isPending 状态自动设置为 true 同时发起异步更新请求。

在 updateName 异步更新请求完成后,React 会自动将 isPending 重置为 false 从而自动控制 button 的禁用状态。

useActionState

在 React19 中,对于表单提交行为的 Action React 提供了更加便捷的方式:

import { useActionState } from 'react';

function updateName(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      Math.random() > 0.5 ?  resolve("表单更新完成":name) : reject();
    }, 200);
  });
}

export default function ChangeName() {
  const [state, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
    //previousState:是上一次的值
      try {
        const result = await updateName(formData.get('name'));
        return result;
      } catch (e) {
        return e;
      }
    },
    null
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        Update
      </button>
      <p>{state}</p>
    </form>
  );
}

 


useActionState 接受一个函数(“Action”),同时返回被包装好的 Action 方法(submitAction)。

当调用被包装好的 submitAction 方法时,useActionState 返回的第三个 isPending 用于控制当前是否为 isPending (被执行状态),同时在 Action 执行完毕后 useActionState 会自动将 Action 的返回值更新到 state 中。


useFormStatus

在 react-dom 库中提供了一个全新的 Hook useFormStatus 可以帮助我们更好地控制创建的表单,用于在表单内部的元素来获取到表单当前状态:

import { useFormStatus } from "react-dom";

function Submit() {
  const { pending, data, method, action } = useFormStatus();
  return (
    <button disabled={pending}>
      {pending ? "正在提交..." : "提交完成"}
    </button>
  );
}

const formAction = async () => {
  // 模拟延迟 3 秒
  await new Promise((resolve) => setTimeout(resolve, 3000));
};

const FormStatus = () => {
  return (
    <form action={formAction}>
      <Submit />
    </form>
  );
};

export default FormStatus;

 


  • pending:如果表单处于待处理状态,则为 true,否则为 false。
  • data:一个实现了 FormData 接口的对象,其中包含表单提交的数据。
  • method:HTTP 方法 – GET或 POST
  • action:一个函数引用。


useOptimistic

React 19 引入了useOptimistic 来管理乐观更新。

所谓 Optimistic updates(乐观更新) 是一种更新应用程序中数据的策略,这种策略通常会理解先修改前端页面,然后再向服务器发起请求。

  • 当请求成功后,则结束操作。
  • 当请求失败后,则会将页面 UI 回归到更新前的状态。

useOptimistic的主要目的是允许我们假设异步操作成功,并在等待实际结果时相应地更新状态。这种做法的好处是可以防止新旧数据之间的跳转或闪烁,提供更快的用户体验。

比如,在绝大多数提交表单的场景中。通常在某个 input 输入完毕后,我们需要将 input 的值输入提交到后台服务中保存后再来更新页面 UI ,这种情况就可以使用 useOptimistic 来进行所谓的“乐观更新”。

 import { useOptimistic, useRef } from "react";

export async function isMessage(message) {
  await new Promise((resolve, reject) =>
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve();
      } else {
        reject();
      }
    }, 1000)
  );
  return message;
}

export function Thread({ messages, sendMessage }) {
  const formRef = useRef();
  async function formAction(formData) {
    addOptimisticMessage(formData.get("message"));
    formRef.current.reset();
    await sendMessage(formData);
  }
  //入参一:初始状态和未进行任何操作时返回的状态。
  //入参二:一个纯函数,它获取当前状态和addOptimistic传递的乐观值,返回合并后的乐观状态。
  //optimisticState:乐观状态。如果没有正在进行的操作,则等于状态;否则,它等于updateFn的结果。
  //addOptimistic:用于触发乐观更新的函数,接受任何类型的 optimisticValue 并将其传递给 updateFn。
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );
  console.log(optimisticMessages, "1");
  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}


import { Thread, isMessage } from "./Thard";
import { useState } from "react";

function App() {
  const [messages, setMessages] = useState([
    { text: "Hello there!", sending: false, key: 1 },
  ]);
  async function sendMessage(formData) {
    try {
      const sentMessage = await isMessage(formData.get("message"));
      setMessages((messages) => [...messages, { text: sentMessage }]);
    } catch (e) {
      console.error(e);
    }
  }
  return <Thread messages={messages} sendMessage={sendMessage} />;
}

export default App;

 


上边的例子中我们使用 useOptimistic 来每次表单提交发送数据前调用 addOptimisticMessage 将页面立即更新。

在isMessage使用Math.random()和延时器模拟了一个请求的成功或者失败,之后等待 isMessage 异步方法完成后,useOptimistic 会根据异步方法是否正常执行完毕从而进行是否保留 useOptimistic 乐观更新后的值。

  • 当 sendMessage Promise Resolved 后,useOptimistic 会更新父组件中的 state 保留之前乐观更新的值
  • 当 sendMessage Promise Rejected 后,useOptimistic 并不会更新 App 中的 state 自然也会重置页面中的值

useOptimistic 的应用范围也很广,例如表单提交、点赞、书签、删除以及其他需要立即反馈的场景。



forwardRef的改进

从 React 19 开始,现在可以将 ref 通过 props 在父子组件中进行传递,这能简化代码,forwardRef 也成了一个即将要被废弃的 API。(但是如果要通过ref实现调用子组件中的方法的话,仍然需要useImperativeHandle这个hooks将方法暴露出去)


 import React, { forwardRef } from 'react';
import {Button} from '@douyinfe/semi-ui'
const ExampleButton = forwardRef((props, ref) => (
  <Button ref={ref}>
    {props.children}
  </Button>
));


之后的写法
 
 import React from 'react';
import {Button} from '@douyinfe/semi-ui'
const ExampleButton = ({ ref, children }) => (
  <Button ref={ref}>
    {children}
  </Button>
);

 
 

Context简化

const ThemeContext = createContext('');

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

 

refs的优化

import { useRef } from 'react';

function ExampleComponent() {
  const myRef = useRef(null);

  function handleRef(node) {
    // 在创建ref/卸载DOM时调用
    if (node) {
      // 执行你的操作,比如操作 DOM
      console.log(node);
    } else {
      // 在清除 ref 时调用,此时 node 是 null
      console.log('Ref is cleared');
    }
  }

  return (
    <div ref={handleRef}>
      {/* ... */}
    </div>
  );
}
 


React 编译器

React 编译器是一个**「自动记忆编译器」,可以自动执行应用程序中的所有记忆操作。react19之前的版本,当状态发生变化时,React有时会重新渲染不相干的部分,我们针对此类情况的解决方案一直是「手动记忆化」,**应用useMemo、useCallback和memo API来手动调整React在状态变化时重新渲染的部分。但手动记忆化可能会使代码变得复杂、容易出错、并需要额外的工作来保持更新。React 团队意识到手动优化很繁琐。React 团队创建了React 编译器。React 编译器现在将管理这些重新渲染。有了这个功能,我们不再需要手动处理这个问题。目前React Compiler 仍然处于 experimental 状态。

兼容 Web Components

Web 组件允许我们使用原生 HTML、CSS 和 JavaScript 创建自定义组件,无缝地将它们整合到我们的 Web 应用程序中,就像使用HTML 标签一样。

之前在react中使用 web 组件的方式:

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const button = document.createElement('button');
    button.textContent = this.getAttribute('label') || 'Click me';
    
    button.addEventListener('click', () => {
      const event = new CustomEvent('button-click', {
        detail: { message: 'Button clicked!' }
      });
      this.dispatchEvent(event);
    });

    this.shadowRoot.append(button);
  }
}

customElements.define('my-button', MyButton);

// MyButton.js
import React, { useRef, useEffect } from 'react';

const MyButton = ({ label, onClick }) => {
  const buttonRef = useRef();

  useEffect(() => {
    const buttonElement = buttonRef.current;

    const handleButtonClick = (e) => {
      if (onClick) {
        onClick(e);
      }
    };
    //需要手动绑定事件
    buttonElement.addEventListener('button-click', handleButtonClick);

    // 清除事件监听器避免内存泄漏
    return () => {
      buttonElement.removeEventListener('button-click', handleButtonClick);
    };
  }, [onClick]);

  return <my-button ref={buttonRef} label={label}></my-button>;
};

export default MyButton;