问题场景
假设你有一个普通的 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 写一整套生命周期体操了。