都React 19了,他到底带来了什么?

如果你还在 React 应用的 useEffect里写 fetch请求,那你就做错了。我们每天都听到这种说法。但我们应该怎么做才对呢?好吧,事实证明,React 团队已经清楚地听到了我们的声音。React 19.2 不仅仅是修补异步问题------它通过 use()<Suspense>useTransition()以及现在的 View Transitions,从头重建了异步处理机制。这些基础构建块共同将异步逻辑从一个"必要的麻烦"转变为一流的架构特性。让我带你了解 React 的异步叙事是如何彻底改变的,以及它为什么对你的团队很重要。

旧方式:useEffect + isLoading

让我们从一个展示旧的 useEffect/ fetch组合模式的例子开始:

javascript 复制代码
function ImageViewer() {
  const [imageId, setImageId] = useState(1);
  const [imageData, setImageData] = useState(null);
  const [isPending, setIsPending] = useState(false);

  useEffect(() => {
    setIsPending(true);
    fetchImage(imageId).then((data) => {
      setImageData(data);
      setIsPending(false);
    });
  }, [imageId]);

  return (
    <div>
      <button onClick={() => setImageId((id) => id + 1)}>Next Image</button>
      {isPending ? <span>Loading...</span> : }
    </div>
  );
}

(这里的 fetchImage只是 fetch的一个包装函数,以保持示例简短。)

这个模式确实能用,但你做了很多重复性的工作。例如,你管理了三个状态(imageId, imageData, isPending),而实际上你只是想显示一张图片。加载状态逻辑是手动的,错误处理也常常是事后才考虑。而且很可能,你的代码库中每个异步操作看起来都略有不同。

最糟糕的是,那个 useEffect的依赖数组是个隐患。漏掉一个依赖项,你会得到过时的闭包;包含太多依赖项,又会触发无限循环。这是无数生产环境 bug 的根源。

这并不理想,老实说,用这种代码你能做到的最好程度就是"不搞砸"。而这并不是你想要编写的代码类型。

新的异步基础构建块

React 19.2 给我们提供了一种完全不同的方法。我们不再用 useEffect来管理 Promise,而是直接使用 use()<Suspense>来处理 Promise。

核心模式:use() + <Suspense>

下面是使用 React 新的 useHook 和 Suspense组件组合重写的同一个组件:

javascript 复制代码
import { use, Suspense, useState } from "react";

function ImageViewer() {
  const [imageId, setImageId] = useState(1);
  const [imageDataPromise, setImageDataPromise] = useState(() => fetchImage(1));

  const handleNext = () => {
    const nextId = imageId + 1;
    setImageId(nextId);
    setImageDataPromise(fetchImage(nextId));
  };

  return (
    <div>
      <button onClick={handleNext}>Next Image</button>
      <Suspense fallback={<ImageSkeleton />}>
        <Image imageDataPromise={imageDataPromise} />
      </Suspense>
    </div>
  );
}

function Image({ imageDataPromise }) {
  const image = use(imageDataPromise);
  return (
    <div>
      <h2>{image.title}</h2>
      
    </div>
  );
}

看看这有多简洁。没有 useEffect。没有手动的 isPending状态。父组件中没有条件渲染逻辑。我们存储的是一个 Promise 而不是数据,React 会处理剩下的事情。

但这是如何工作的呢?<Suspense>背后的魔法

<Suspense>尝试渲染其子组件时,<Image>组件会调用 use(imageDataPromise)。如果那个 Promise 还没有解决,use()会直接抛出这个 Promise。是的------像抛出异常一样抛出它。

我知道你在想什么:"用异常来控制流程?这太奇怪了!"但请听我说,因为这其实非常巧妙。

那个被抛出的 Promise 会向上冒泡穿过组件树,直到被 <Suspense>捕获。<Suspense>于是知道:"啊,我的子组件需要从这个 Promise 获取数据。我会显示我的加载状态(fallback),并等待这个 Promise 解决。"

当 Promise 解决后,<Suspense>会重新渲染其子组件,use()返回数据,图片就显示出来了。

这消除了在组件树的每个层级进行条件渲染的需要。加载状态变得声明式,因为你可以用 <Suspense>包装异步组件,并定义在加载时显示什么。React 会处理时机。

更流畅的交互:useTransition() + action

但我们可以让它变得更好。目前,当你点击 "Next Image" 时,按钮在新图片加载期间仍然可点击。这不是很好的用户体验;用户可能会多次点击,造成竞态条件。React 19.2 引入了 action props 和 useTransition()来优雅地处理这种情况:

javascript 复制代码
function Button({ action, children }) {
  const [isPending, startTransition] = useTransition();

  const onClick = () => {
    startTransition(async () => {
      await action();
    });
  };

  return (
    <button onClick={onClick} disabled={isPending}>
      {children}
    </button>
  );
}

function ImageViewer() {
  const [imageId, setImageId] = useState(1);
  const [imageDataPromise, setImageDataPromise] = useState(() => fetchImage(1));

  const handleNext = async () => {
    const nextId = imageId + 1;
    setImageId(nextId);
    setImageDataPromise(fetchImage(nextId));
  };

  return (
    <div>
      <Button action={handleNext}>Next Image</Button>
      <Suspense fallback={<ImageSkeleton />}>
        <Image imageDataPromise={imageDataPromise} />
      </Suspense>
    </div>
  );
}

名为 action的 prop 本身并没有什么特别之处。它只是一个约定,告诉其他开发者这个按钮会将处理器包装在一个过渡(transition)中。

View Transitions:GPU 加速的润色

既然我们谈到了异步的 UI 部分,让我们聊聊 View Transitions。React 19.2(在实验性分支上)添加了对浏览器原生 View Transitions API 的支持。这让你在异步内容加载时能获得如黄油般顺滑的、GPU 加速的动画。而且只需要几行代码。

首先,添加一些 CSS 动画:

css 复制代码
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

::view-transition-old(image-container) {
  animation: fadeOut 0.3s ease-out;
}
::view-transition-new(image-container) {
  animation: fadeIn 0.3s ease-in;
}
::view-transition-old(button-pulse) {
  animation: pulse 0.3s ease-in-out;
}

然后将 View Transitions 添加到你的组件中:

javascript 复制代码
import { experimental_useViewTransition as useViewTransition } from "react";

function Image({ imageDataPromise }) {
  const image = use(imageDataPromise);
  return ;
}

function ImageSkeleton() {
  return <div className="image-skeleton" />;
}

function Button({ action, children, disabled }) {
  const [isPending, startTransition] = useTransition();
  const viewTransition = useViewTransition();

  const onClick = () => {
    startTransition(async () => {
      await action();
    });
  };

  return (
    <div {...viewTransition("button-pulse")}>
      <button onClick={onClick} disabled={isPending}>
        {children}
      </button>
    </div>
  );
}

function ImageViewer() {
  const [imageId, setImageId] = useState(1);
  const [imageDataPromise, setImageDataPromise] = useState(() => fetchImage(1));

  const handleNext = async () => {
    const nextId = imageId + 1;
    setImageId(nextId);
    setImageDataPromise(fetchImage(nextId));
  };

  return (
    <div>
      <Button action={handleNext}>Next Image</Button>
      <div {...useViewTransition("image-container")}>
        <Suspense fallback={<ImageSkeleton />}>
          <Image imageDataPromise={imageDataPromise} />
        </Suspense>
      </div>
    </div>
  );
}

就这样。浏览器会自动处理 GPU 加速的过渡动画。你的骨架屏淡出,你的图片淡入,你的按钮在加载时会脉动。效果非常丝滑,而你几乎没写什么代码。

注意: ​ 你需要 React 19.2 实验版才能使用此功能:npm install react@experimental react-dom@experimental

这对前端团队为何重要

这些改变不仅仅是清理 useEffect/ fetch的烂摊子。它关乎构建一个更优秀的 React,这个 React 终于承认异步是一等公民。

这些模式意味着你的团队编写的代码更容易理解和维护。框架处理了更多事情。UI 响应更迅捷,因为它是按需更新的。你正在利用更多底层框架的能力,为你的应用赋予具有流畅动画的现代感。

这不再是"你爷爷辈的 React"了,是时候让团队开始使用这些新工具了。

关键要点

  • React 19.2 带来了"无处不在的异步"。

  • 这些基础构建块作为一个系统协同工作:

    • use()从 Promise 中提取数据
    • <Suspense>声明式地处理加载状态
    • useTransition()免费为你提供加载标志
    • View Transitions 添加 GPU 加速的润色
  • 旧方式(useEffectfetch)对 React 来说是个严重的痛点,但我们有了很好的替代方案。

  • React 的异步基础构建块意味着更少的代码、更少的 bug 和更好的用户体验。

  • 你的团队可以更专注于构建体验,而不是支撑它的底层管道。

非 19.2 的选择:TanStack Query

话虽如此,如果你还没准备好升级到 React 19.2,有一个很好的替代方案:TanStack Query(前身为 React Query)。它适用于任何 React 版本,并为你提供自动缓存、后台重新获取、乐观更新等功能。它是一个久经考验的解决方案,解决了与 React 19.2 异步基础构建块相同的问题,只是 API 不同。有充分的理由说明为什么这么多 React 应用都安装了 react-query

下一步:React 19.2 引领的前端未来

React 19.2 已经发布。它是稳定的。你应该将其用于生产环境(除了仍在完善中的 View Transitions)。

异步变革已经到来。React 终于解决了它最大的痛点,框架也因此变得更好。

如果你还没有探索过 React 19.2,现在是时候了。在一个副项目上试试 use()<Suspense>,看看异步逻辑变得多么简单。你会惊讶于自己以前没有它是怎么过的。

相关推荐
洞窝技术31 分钟前
一键屏蔽某国IP访问实战
前端·nginx·node.js
fruge43 分钟前
前端自动化脚本:用 Node.js 写批量处理工具(图片压缩、文件重命名)
前端·node.js·自动化
Jolyne_1 小时前
antd Image base64缓存 + loading 态优化方案
前端
BINGCHN1 小时前
NSSCTF每日一练 SWPUCTF2021 include--web
android·前端·android studio
Z***u6592 小时前
前端性能测试实践
前端
xhxxx2 小时前
prototype 是遗产,proto 是族谱:一文吃透 JS 原型链
前端·javascript
倾墨2 小时前
Bytebot源码学习
前端
用户93816912553602 小时前
VUE3项目--集成Sass
前端
S***H2832 小时前
Vue语音识别案例
前端·vue.js·语音识别