欢迎光临
我们一直在努力

use的理解与应用

  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 时,它会做一件神奇的事情:​

  • 如果 Promise 正在 pending,它会“抛出”这个 Promise。​
  • 这个“抛出”的行为会被最近的 <Suspense> 边界捕获,并显示 fallback UI。​
  • 当 Promise resolve 后,React 会重新尝试渲染该组件,此时 use Hook 会返回 Promise 的结果值。​
  • 如果 Promise reject,错误则会被最近的 <ErrorBoundary> 捕获。
  • 一句话: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>
    );
    }

    赞(0)
    未经允许不得转载:171主机测评 » use的理解与应用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址