React 19 `use()` 来了:以后数据加载可以不用 useEffect?

问题场景

假设你有一个普通的 React 组件,要从 API 加载用户数据:

tsx 复制代码
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Skeleton />;
  return <div>{user.name}</div>;
}

这段代码写了 15 行,就为干一件事:等数据回来再渲染。而且你还要处理:

  • 竞态(后发的请求覆盖了先发的)
  • 错误边界
  • 内存泄漏(卸载后还在 setState)

这些问题在过去几年催生了 React Query、SWR 等库。但 React 19 说:这活我能干了。

原因分析

React 团队在 19 版本引入了一个全新的 API------use()。它不是 Hooks,不能在条件/循环外的那套规则限制它(虽然实际上最好也别那么干)。它的签名是:

ts 复制代码
function use<T>(promise: Promise<T>): T;
function use<T>(context: Context<T>): T;

use() 做了什么?

  • 传入一个 Promise → 挂起组件 直到 resolve,配合 <Suspense> 展示 fallback
  • 传入一个 Context → 等价于 useContext,但可以在条件语句和循环里用

关键区别:use() 在渲染期间直接消费 Promise,React 会帮你处理"等待→重渲染"的流程,不再需要 useEffect + useState 那套样板代码。

解决方案

1. 用 use() 替代 useEffect 数据加载

先写一个获取 Promise 的工具:

tsx 复制代码
// fetchUser.ts
let cache = new Map<string, Promise<User>>();

export function fetchUser(id: string): Promise<User> {
  if (!cache.has(id)) {
    cache.set(id, fetch(`/api/users/${id}`).then(r => r.json()));
  }
  return cache.get(id)!;
}

然后在组件里直接用 use()

tsx 复制代码
import { use, Suspense } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const user = use(fetchUser(userId)); // 👈 直接等
  return <div>{user.name} --- {user.email}</div>;
}

// 父组件包 Suspense
function Page({ userId }: { userId: string }) {
  return (
    <Suspense fallback={<Skeleton />}>
      <UserProfile userId={userId} />
    </Suspense>
  );
}

15 行 → 3 行 。竞态问题?React Suspense 天然解决------每次渲染都 use(fetchUser(userId)),如果 userId 变了,老的 Promise 被丢弃,新的挂起,不存在覆盖问题。

2. 那缓存怎么办?

上面的 fetchUser 用了最简的 Map 缓存。生产环境建议配合 React 19 的 cache()

ts 复制代码
import { cache } from 'react';

export const fetchUser = cache(async (id: string) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

cache() 保证同样的参数只调用一次函数,结果自动 dedupe。Server Components 里用这个极其丝滑。

3. use(context) --- 条件中使用 Context

过去你不能在 if 里用 useContext,但用 use() 可以:

tsx 复制代码
function Sidebar({ showTheme }: { showTheme: boolean }) {
  // ✅ 条件中读 Context
  const theme = showTheme ? use(ThemeContext) : 'light';

  // ✅ 循环中读 Context
  return items.map(item => {
    const config = use(ConfigContext);
    return <Item key={item.id} config={config} />;
  });
}

这对高阶组件、渲染函数场景非常实用。

要点总结

对比 传统写法 (useEffect) use() + Suspense
代码量 12--20 行 3--5 行
竞态处理 手动 cleanup 天然解决
加载状态 手动 state Suspense fallback
错误处理 手动 try/catch + state ErrorBoundary
条件中读 Context ❌ 不行 ✅ 可以

⚠️ 注意:

  • use() 还在 Experimental,可以用 React 19 RC 尝鲜。生产环境等正式版再上。
  • use(promise) 必须在 <Suspense> 内部才能挂起,没有 Suspense 兜底会抛错。
  • 客户端组件中 use() + cache() 需要搭配 React 19 的数据获取方案,目前 Server Components 体验最成熟。
  • use() 不是什么都能替代------事件处理、定时器、副作用逻辑还是得用 useEffect

一句话总结: 如果你的组件只做一件事------"等数据,然后渲染",use() 是过去 5 年 React 数据加载最优雅的解法。再也不用为了一个 fetch 写一整套生命周期体操了。

相关推荐
没落英雄1 小时前
从零开始搭建一个 AI Agent —— LangChain + TypeScript 实战手记
前端·人工智能·架构
远航_1 小时前
git submodule
前端·后端·github
摸着石头过河的石头1 小时前
从 Webpack 到 RSBuild:前端构建工具的进化之路
前端
疯狂的魔鬼1 小时前
告别 boolean 地狱:一个文件上传组件的状态机实践
前端·设计
竹林8181 小时前
Solana DApp 开发踩坑实录:从零用 @solana/web3.js 实现链上数据查询与交易签名
前端·javascript
狂师1 小时前
测试工程师的AI 技能库:推荐5个让你效率翻倍的Skills
前端·后端·测试
李明卫杭州1 小时前
Vue3 watch 与 watchEffect 深度解析
前端
CodeSheep1 小时前
DeepSeek正式官宣摇人,夯!
前端·后端·程序员
用户059540174461 小时前
Redis持久化踩坑实录:这个数据丢失Bug让我排查了6小时
前端·css