use(很多人口头叫 “use hook”,但官方把它称为 use API)你可以把它当成“在 render 期间读取资源的统一入口”:读取 Promise(异步结果)或 Context(上下文),并且天然接入 Suspense(加载态)和 Error Boundary(错误态)。
use的应用场合:
-
适合数据获取简单的场景
-
复杂的错误处理和加载状态交给边界组件
-
让业务组件专注于数据展示逻辑
1)use 是什么(核心概念)
与传统 Hook(如 useState、useEffect)不同,它不受 Hook 规则限制——可以在条件语句、循环等任何地方使用。
use Hook:在渲染中读取 Promise 和 Context
use Hook 是一个可以在渲染期间“解包”数据源的 Hook。目前它支持两种数据源:Promise 和 Context。
与其他的 Hooks 不同,use 可以在条件语句、循环或普通函数中调用,这赋予了它前所未有的灵活性。
当 use 被用于一个 Promise 时,它会做一件神奇的事情:
一句话:use = “render 里的读取器”,Promise 没好就 suspend,好了就给值,错了就 throw。
2)为什么要用 use,而不是 useEffect + useState?
useEffect + useState 的典型写法需要你自己维护:
-
loading / error / data 三套状态
-
effect 依赖数组、竞态处理、取消请求等边角
而 use(promise) 把“加载/错误/成功”的控制权交给 React 的 Suspense + Error Boundary:
-
use 的解决方案:直接接收一个 Promise,并像 await 一样等待其解析。
-
如果 Promise 成功,返回数据。
-
如果 Promise 失败,抛出错误,需要外部捕获。
-
3)代码简化示例:两行读取 Promise(配合 Suspense)
传统方式:需要定义状态(loading、error、data)和 useEffect 执行请求。
// 传统方式
function UserData({ userId }) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(json => {
setData(json);
setError(null);
})
.catch(err => {
setError(err);
setData(null);
})
.finally(() => {
setLoading(false);
});
}, [userId]);
if (loading) return <div>加载中…</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>{data.name}</div>;
}
使用 use:
核心写法就是一行:
const data = use(fetch(url));
仅一行即可获取数据,无需手动管理状态。
// 使用 use Hook
function UserData({ userId }) {
const data = use(fetch(`/api/user/${userId}`).then(res => res.json()));
return <div>{data.name}</div>;
}
注意:Suspense 的 fallback 会在 Promise pending 时显示。
4)加载与错误处理
加载状态:通过 <Suspense> 组件处理。当 use 中的 Promise 未解析时,显示 fallback 内容。
错误处理:通过 <ErrorBoundary> 组件捕获并显示错误回退界面。
当然了其实我们也可以自己来设计加载状态,错误处理UI,可以参考的我的另一篇文章。
这里我们使用默认
App(加载 + 错误处理 + “两行读取 Promise”)
import { Suspense, use } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
// 一个获取数据的函数,它返回一个 Promise
const fetchMessage = () => {
return new Promise(resolve => setTimeout(() => resolve("Hello from the future!"), 2000));
};
// Message 组件在渲染时“读取”Promise
const Message = ({ messagePromise }) => {
// 在渲染期间直接 use(promise)
const message = use(messagePromise);
return <p>Message: {message}</p>;
};
// App 组件管理 Promise 的创建和 Suspense 边界
const App = () => {
// 在渲染开始前就创建 Promise
const messagePromise = fetchMessage();
return (
<div>
<h1>My App</h1>
<ErrorBoundary fallback={<p>Oops, something went wrong.</p>}>
<Suspense fallback={<p>⏳ Loading message…</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
</div>
);
};
这种模式被称为“Render-as-you-fetch”。我们不再需要在 useEffect 中获取数据,也无需手动管理 loading 状态。数据获取的请求在渲染开始时就已发出,组件则声明式地等待数据就位。这避免了网络请求的瀑布流问题,并使得数据加载的 UI 逻辑变得异常简洁和健壮。
5)use 的“规则”(为什么说它无视传统 Hook 规则)
传统 Hook(useState/useEffect/useContext…)必须在组件顶层调用,不能放进 if/for;原因是 Hook 状态依赖稳定的调用顺序。
但 use 不同:它“可以在 if、循环里调用”。这也是它最大的语法自由度。
同时也有硬约束:
-
调用 use 的函数本身必须是组件或自定义 Hook(不能在普通函数里随便用)。
-
use 只能在 render 期间调用(它是 render-time API)。
6. use 的其他用途
接收 Context:可以替代 useContext,且同样不受条件限制。
const value = use(MyContext);
例子:
import React, { createContext, use, useState } from "react";
const ThemeContext = createContext("light");
function Panel({ enabled }) {
// 注意:use 可以放在 if 里(useContext 不允许这样写)
if (!enabled) return <div>Panel disabled</div>;
const theme = use(ThemeContext);
return <div>Panel theme: {theme}</div>;
}
export default function App() {
const [enabled, setEnabled] = useState(true);
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={theme}>
<div style={{ padding: 12 }}>
<button onClick={() => setEnabled((v) => !v)}>
Toggle Panel (enabled: {String(enabled)})
</button>
<button style={{ marginLeft: 8 }} onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}>
Toggle Theme (theme: {theme})
</button>
<div style={{ marginTop: 12 }}>
<Panel enabled={enabled} />
</div>
</div>
</ThemeContext.Provider>
);
}
7.具体例子
示例1: 基础数据获取
传统方式:
function UserProfile({ userId }) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>加载中…</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>{user.name}</div>;
}
使用 use Hook:
function UserProfile({ userId }) {
const user = use(fetch(`/api/users/${userId}`).then(res => res.json()));
return <div>{user.name}</div>;
}
// 在父组件中
function App() {
return (
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中…</div>}>
<UserProfile userId={123} />
</Suspense>
</ErrorBoundary>
);
}
示例2: 条件数据获取
function UserDashboard({ userId, showPosts }) {
// 条件性获取用户数据
const user = use(
showPosts
? fetch(`/api/users/${userId}`).then(res => res.json())
: Promise.resolve({ name: '未加载' })
);
// 条件性获取用户帖子
let posts = [];
if (showPosts) {
posts = use(fetch(`/api/users/${userId}/posts`).then(res => res.json()));
}
return (
<div>
<h2>{user.name}</h2>
{showPosts && (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)}
</div>
);
}
示例3: 并行数据获取
function UserDashboard({ userId }) {
// 并行获取多个数据源
const [user, posts, comments] = use(Promise.all([
fetch(`/api/users/${userId}`).then(res => res.json()),
fetch(`/api/users/${userId}/posts`).then(res => res.json()),
fetch(`/api/users/${userId}/comments`).then(res => res.json())
]));
return (
<div>
<h1>{user.name}</h1>
<h2>帖子 ({posts.length})</h2>
<h2>评论 ({comments.length})</h2>
</div>
);
}
示例4: 上下文的条件使用
const ThemeContext = React.createContext('light');
const UserContext = React.createContext(null);
function ThemedButton({ showUserInfo }) {
const theme = use(ThemeContext);
const themeStyle = theme === 'dark' ? styles.dark : styles.light;
let userInfo = null;
if (showUserInfo) {
const user = use(UserContext);
userInfo = <span> – {user?.name}</span>;
}
return (
<button style={themeStyle}>
主题按钮 {userInfo}
</button>
);
}
示例5: 错误处理具体示例
// 错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
// 可以显示自定义错误界面
return this.props.fallback || <div>出错了: {this.state.error.message}</div>;
}
return this.props.children;
}
}
// 使用示例
function ProductPage({ productId }) {
const product = use(
fetch(`/api/products/${productId}`).then(res => {
if (!res.ok) {
throw new Error(`产品 ${productId} 不存在`);
}
return res.json();
})
);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// 应用入口
function App() {
return (
<ErrorBoundary fallback={<div>产品页面加载失败,请稍后重试</div>}>
<Suspense fallback={<div>加载产品中…</div>}>
<ProductPage productId={123} />
</Suspense>
</ErrorBoundary>
);
}
示例6: 缓存和优化
// 简单的缓存实现
const cache = new Map();
function fetchWithCache(url) {
if (!cache.has(url)) {
cache.set(url, fetch(url).then(res => res.json()));
}
return cache.get(url);
}
function CachedComponent({ resourceId }) {
// 相同的 resourceId 会返回缓存的 Promise
const data = use(fetchWithCache(`/api/resource/${resourceId}`));
return <div>{data.name}</div>;
}
示例7: 获取钱包余额和NFT数据
// app/profile/page.tsx – 客户端组件
'use client';
import { use } from 'react';
import { Suspense } from 'react';
import { ethers } from 'ethers';
// 获取以太坊余额
function WalletBalance({ address }: { address: string }) {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const balance = use(provider.getBalance(address));
return (
<div>
<h3>余额</h3>
<p>{ethers.utils.formatEther(balance)} ETH</p>
</div>
);
}
// 获取用户NFT
function UserNFTs({ address }: { address: string }) {
const nfts = use(fetchNFTs(address)); // 假设的NFT API
return (
<div>
<h3>NFT 收藏</h3>
<div className="grid grid-cols-3 gap-4">
{nfts.map((nft: any) => (
<img
key={nft.tokenId}
src={nft.image}
alt={nft.name}
className="rounded-lg"
/>
))}
</div>
</div>
);
}
// 主组件
export default function ProfilePage({ params }: { params: { address: string } }) {
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">钱包详情</h1>
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div className="flex justify-center"><Spinner /></div>}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<WalletBalance address={params.address} />
<UserNFTs address={params.address} />
</div>
</Suspense>
</ErrorBoundary>
</div>
);
}
示例8: 智能合约数据获取
// app/contract/page.tsx
'use client';
import { use } from 'react';
import { ethers } from 'ethers';
import contractABI from '@/contracts/ERC20.json';
const CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_TOKEN_ADDRESS;
// 自定义Hook:获取代币数据
function useTokenData() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const contract = new ethers.Contract(CONTRACT_ADDRESS!, contractABI, provider);
// 并行获取所有合约数据
const [name, symbol, totalSupply] = use(Promise.all([
contract.name(),
contract.symbol(),
contract.totalSupply(),
]));
return { name, symbol, totalSupply };
}
// 获取代币持有者信息
function useTokenHolders(limit: number = 10) {
const provider = new ethers.providers.Web3Provider(window.ethereum);
// 从The Graph或自定义API获取数据
const holders = use(
fetch(`/api/token/holders?limit=${limit}`)
.then(res => res.json())
);
return holders;
}
export default function TokenDashboard() {
const { name, symbol, totalSupply } = useTokenData();
const holders = useTokenHolders(5);
return (
<div className="p-6">
<div className="bg-white rounded-lg shadow p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">{name} ({symbol})</h2>
<p>总供应量: {ethers.utils.formatUnits(totalSupply, 18)}</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold mb-4">前5大持有者</h3>
<div className="space-y-3">
{holders.map((holder: any) => (
<div key={holder.address} className="flex justify-between items-center">
<span className="font-mono text-sm">{holder.address.slice(0, 10)}…{holder.address.slice(-8)}</span>
<span>{holder.balance}</span>
</div>
))}
</div>
</div>
</div>
);
}





