在 React 19 中使用新的 use() Hook,实现更简洁的 Async Components
Source: Dev.to
如果你已经使用 React 一段时间了,可能已经体会到在组件中处理异步数据的头疼。useEffect、useState 以及各种数据获取库的组合,往往会让代码变得杂乱。React 19 引入了一个改变游戏规则的解决方案:use() Hook。
让我来展示一下这个新 Hook 如何简化异步数据处理,让你的代码变得更加清晰。
旧方式:冗长且易出错
在 React 19 之前,在组件内部获取数据的写法大致如下:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return ;
if (error) return ;
return {user.name};
}这段代码为了一个简单的数据获取就写了大量模板代码。你必须管理三种不同的状态,正确处理 effect 的依赖,并且希望没有遗漏任何边缘情况。
新方式:简洁且声明式
React 19 的 use() Hook 改变了一切。它在 promise 解析期间挂起组件,在组件树的更高层处理加载和错误状态。
import { use } from 'react';
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return {user.name};
}就是这样。无需状态管理、无需副作用、无需样板代码。组件只会等待 promise 解析。
实际工作原理
use() Hook 接受一个 Promise,并且:
- 挂起 组件(如果 Promise 仍在 pending 状态)
- 恢复 并使用已解析的值(当 Promise 完成时)
- 抛出 错误(如果 Promise 被拒绝),你可以在错误边界中捕获它
下面是一个完整示例,展示真实的数据获取场景:
import { use, Suspense, ErrorBoundary } from 'react';
// Async function that returns a promise
async function fetchUser(userId) {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
}
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return (
<>
<h2>{user.name}</h2>
<p>{user.email}</p>
<span>{user.role}</span>
</>
);
}
// Wrap it with Suspense and ErrorBoundary
function App() {
return (
<ErrorBoundary fallback={<div>Something went wrong.</div>}>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId="123" />
</Suspense>
</ErrorBoundary>
);
}处理多个异步值
其中一个最佳特性是 use() 优雅地处理多个 Promise:
function Dashboard({ userId }) {
const [user, posts] = use(Promise.all([
fetchUser(userId),
fetchPosts(userId)
]));
return (
<>
<h2>Welcome, {user.name}</h2>
{/* render posts here */}
</>
);
}不再需要 Promise.all + useState + useEffect 组合——直接在组件中编写异步代码即可。
与现有模式集成
您仍然可以在已有的数据获取库中使用 use()。例如,配合 TanStack Query:
import { useQuery } from '@tanstack/react-query';
import { use } from 'react';
function UserAvatar({ userId }) {
const query = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId)
});
// 转换为 Promise 供 use() 使用
const user = use(
query.isSuccess ? Promise.resolve(query.data) : new Promise(() => {})
);
return <img src={user.avatar} alt={user.name} />;
}或者创建一个简单的包装函数:
function useAsync(asyncFn) {
return use(Promise.resolve().then(() => asyncFn()));
}
function UserCard({ userId }) {
const user = useAsync(() => fetchUser(userId));
return {user.name};
}正确的错误处理
use() 钩子与 React 的错误边界(Error Boundary)模式完美结合:
function PostWithComments({ postId }) {
const post = use(fetchPost(postId));
const comments = use(fetchComments(postId));
return (
<>
<h2>{post.title}</h2>
<p>{post.content}</p>
{/* render comments here */}
</>
);
}
// 组件树中的任何错误都会冒泡到错误边界
function ErrorFallback({ error }) {
return <div>出现了一些问题:{error.message}</div>;
}
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<div>加载中…</div>}>
<PostWithComments postId="456" />
</Suspense>
</ErrorBoundary>
);
}何时使用 use() 与 useEffect
使用 use() 的情况:
- 需要获取在组件中渲染的数据。
- 想要更简洁、更易读的异步代码。
- 正在构建时考虑使用 Suspense。
坚持使用 useEffect 的情况:
- 处理副作用(日志、分析、订阅)。
- 数据不直接影响渲染输出。
为什么在组件中渲染?
- 您需要对 何时 获取进行细粒度控制。
更宏观的视角
use() hook 不仅仅是语法糖——它是 React 对 “React 19:异步时代” 的愿景的一部分。
结合 Server Components(服务器组件)和 Actions(动作),它标志着我们构建 React 应用方式的根本转变。
不再需要在每个组件中单独管理加载状态,而是只在顶层定义一次,让 React 负责其余的工作。
这提供了更简洁的思维模型,并且可以减少代码量。
试一试
如果你使用 React 19(或 beta 版),可以开始在组件中尝试 use()。你会惊讶于样板代码消失得多快,以及你的异步代码变得多么易读。
React 的未来是原生异步的。拥抱它吧。
你对新的 use() Hook 有何看法?
你已经尝试过了吗?在下方评论区留下你的想法吧。
