对比:useEffect vs use处理Promise
代码题:用use改写数据请求逻辑
React 19 use
钩子:异步操作革命性简化方案(附完整代码对比)
一、useEffect
vs use
处理 Promise 核心差异对比
对比维度 | useEffect 方案 |
use 钩子方案 |
---|---|---|
代码复杂度 | 高(需手动管理 loading/error/data 状态) | 低(自动挂起组件直到 Promise 完成) |
状态更新 | 需手动调用 setState 更新多个状态 |
直接返回 Promise 结果(类似同步写法) |
条件渲染 | 无法在条件语句中直接使用 Hooks | 支持任意位置调用(包括条件/循环语句) |
错误处理 | 需 try/catch 或额外错误状态 |
通过 ErrorBoundary 全局捕获 |
组件树管理 | 每个组件单独处理加载状态 | 通过父级 <Suspense> 统一管理加载状态 |
请求取消 | 需手动实现 AbortController | 自动处理组件卸载时的请求取消 |
二、代码对比:商品列表加载案例
1. 传统 useEffect
实现(React 18)
jsx
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchProducts = async () => {
try {
const res = await fetch('/api/products', {
signal: controller.signal
});
const data = await res.json();
setProducts(data);
setLoading(false);
} catch (err) {
if (!controller.signal.aborted) {
setError(err.message);
setLoading(false);
}
}
};
fetchProducts();
return () => controller.abort();
}, []);
if (loading) return <Spinner />;
if (error) return <ErrorAlert message={error} />;
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
2. use
钩子实现(React 19)
jsx
// 子组件:纯粹展示数据
function ProductList({ productsPromise }) {
const products = use(productsPromise);
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
// 父组件:统一管理异步状态
function App() {
return (
<ErrorBoundary fallback={<ErrorAlert />}>
<Suspense fallback={<Spinner />}>
<ProductList
productsPromise={fetch('/api/products').then(res => res.json())}
/>
</Suspense>
</ErrorBoundary>
);
}
关键优化点:
- 代码行数减少 60%(从 25 行 → 10 行)
- 状态管理逻辑完全移除
- 错误处理与加载状态全局复用
- 自动支持请求取消(组件卸载时)
三、use
钩子高级用法示例
1. 并行请求优化
jsx
function UserDashboard({ userId }) {
// 并行获取多个资源
const [user, orders] = use(
Promise.all([
fetchUser(userId),
fetchOrders(userId)
])
);
return (
<div>
<h1>{user.name}</h1>
<OrderList data={orders} />
</div>
);
}
2. 动态链式请求
jsx
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
// 根据第一个请求结果发起第二个请求
const posts = use(fetchPosts(user.postsId));
return (
<div>
<Avatar url={user.avatar} />
<PostList data={posts} />
</div>
);
}
3. 条件请求(突破 Hooks 规则限制)
jsx
function SmartComponent({ useCache }) {
// 根据参数动态决定是否发起请求
const data = use(
useCache ? getCacheData() : fetchNewData()
);
return <DataViewer data={data} />;
}
四、最佳实践与升级策略
-
渐进式迁移路线
-
性能优化组合拳
jsx// 搭配 React Query 缓存策略 const { data } = useQuery({ queryKey: ['products'], queryFn: () => fetch('/api/products').then(res => res.json()) }); // 在需要渲染的地方使用 use const products = use(data);
-
服务端组件集成(Next.js 14+)
jsx// app/page.js async function fetchData() { const res = await db.query('SELECT * FROM products'); return res.json(); } export default function Page() { const data = use(fetchData()); return <ProductList data={data} />; }
五、避坑指南:从 useEffect
到 use
的注意事项
-
依赖项处理变化
diff- useEffect(() => { fetchData(id) }, [id]) + const data = use(fetchData(id)) // ID变化自动触发重新请求
-
不可中断的副作用
jsx// 使用 use 处理副作用需谨慎 const analytics = use(sendAnalytics()); // ❌ 可能重复发送
-
类组件兼容方案
jsxclass LegacyComponent extends React.Component { render() { // 通过高阶组件转换 return withSuspense(<ModernUseComponent />); } }
通过 use
钩子,React 19 将异步操作从 命令式编程 转变为 声明式资源消费 ,建议在以下场景优先采用:
• 数据加载占 80% 以上的页面
• 需要深度嵌套 Loading 状态的复杂布局
• 需要与 Suspense 流式渲染配合的增量加载