React 的 use() API 或将取代 useContext

434 阅读6分钟

本篇依然来自于我们的 《前端周刊》 项目!

由团队成员 嘿嘿 翻译,他的文章风格稳健而清晰,注重结构与逻辑的严谨性,善于用简洁的语言将复杂技术拆解成易于理解的知识点~

欢迎大家 进群 与他探讨 React 最新趋势,并持续追踪全球前端领域的最新动态!

原文地址:React’s use() API is about to make useContext obsolete

image.png

引言

多年来,React 开发者习惯使用 useContext 在全局共享状态,而不用把 props 一层层传递下去。虽然这种方式可行,但即将到来的 use() API 带来了更强大的能力,远远超越了单纯读取 context 的功能。

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/37c64835bac14282ac17851601a6343e~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5o6Y6YeR5a6J5Lic5bC8:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTUyMTM3OTgyMzM0MDc5MiJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1755847555&x-orig-sign=7KPfYn%2FoxhCp8LcJ0XtlDj37M18%3D

通过 use(),你不仅能做到 useContext 可以做到的一切,还能直接在组件中无缝处理异步数据,比如 API 调用或 Promise。这意味着更统一的写法、更干净的代码,以及一种全新的状态管理和数据获取思路。

在本文结束时,你将清楚理解 use() API 如何改善现代 React 中的状态管理和数据获取方式。

前置条件

在继续之前,你需要准备:

  • 已在本地安装 Node.js v24
  • React v19

项目初始化

我们用 Vite 来初始化一个 React 项目。 打开终端并运行:

npm create vite@latest my-project

接着进入项目目录:

cd my-project

然后启动开发服务器:

npm run dev

本文示例会使用 Tailwind CSS 来做样式。

在 use() 之前

在 React 的新 use() API 出现之前,若要从 API 获取数据,你需要导入并使用三个 Hook,例如:

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

这种写法显得冗长:你需要同时管理三个状态变量(dataloadingerror),并结合 useEffect 来触发请求,例如:

import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
          throw new Error('Failed to fetch data');
        }
        const result = await response.json();
        setData(result);
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, []);

  if (loading) {
    return <div>加载中...</div>;
  }

  if (error) {
    return <div>错误: {error.message}</div>;
  }

  return (
    <div>
      {/* 在此渲染数据 */}
    </div>
  );
}

这个组件通过 useEffect 拉取 API 数据,并用 useState 管理状态。它初始化了三个状态:data 存放结果,loading 表示加载状态,error 捕获请求错误。当组件挂载时,会触发异步函数请求数据,成功则存入 data,失败则存入 error

使用 use()

接下来看 use() 的场景。使用 use,你不再需要手动管理这三个状态,也不需要 useEffect 来触发请求。

use() 可以直接在组件中读取 Promise 的值,无需 useEffectuseState 来单独管理 loading 和 error。与普通 Hook 不同,它可以在循环和条件语句中运行,但依然必须在组件或 Hook 内部调用。

结合 Suspense,它可以以最小的配置实现丝滑的加载体验。语法如下:

const value = use(resource);

构建 Demo 应用

我们来实际体验一下 use 的威力。

设置 API 调用src 文件夹中创建 api.js,粘贴如下代码:

let usersPromise;

async function fetchUsers() {
  const response = await fetch('<https://jsonplaceholder.typicode.com/users>');
  if (!response.ok) {
    throw new Error('Failed to fetch users');
  }
  return response.json();
}

export function getUsersPromise() {
  if (!usersPromise) {
    usersPromise = fetchUsers();
  }
  return usersPromise;
}

上面的 API 调用中,我们通过 getUsersPromise 来缓存(memoize)Promise,这样即便组件重新渲染,数据也只会请求一次。接着我们用 use 来解包这个 Promise。

使用 use 解包 Promise

创建 UserList.jsx 并添加如下导入:

import React, { use } from 'react';
import { getUsersPromise } from './api';

然后写组件:

function UserList() {
  const users = use(getUsersPromise());
  return (
    <div>
      <h1 className='text-center py-5 text-2xl font-bold'>用户列表</h1>
      <ul className='grid grid-cols-5 gap-5'>
        {users.map((user) => (
          <li key={user.id} className=' bg-gray-500 py-10 text-center rounded-[5px]  text-gray-200'>
            <strong>{user.name}</strong> - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

这里我们把 getUsersPromise 传给了 use,这样它会直接解包返回的 Promise,并赋值给 users。接着我们遍历并渲染数据。

你注意到没有?这里完全不需要 useStateuseEffect! 得益于 use,我们可以声明式地处理数据请求、渲染,以及加载与错误处理。接下来我们还会结合 SuspenseErrorBoundary 来进一步优化用户体验。


错误边界(ErrorBoundary)

use API 借助 SuspenseErrorBoundary 来管理不同状态。由于 React 没有内置 ErrorBoundary,我们需要自己实现。 在项目中新建 ErrorBoundary.jsx 并粘贴以下代码:

import React from "react";

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error: error };
  }

  componentDidCatch(error, errorInfo) {
    console.error("未捕获错误:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="p-10 text-center text-red-700 font-bold border-2 border-solid border-red-800">
          <h2>出错了。</h2>
          <p>{this.state.error && this.state.error.message}</p>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

该组件能捕获子组件树中任意错误,使用了两个生命周期方法:

  • static getDerivedStateFromError(error)
  • componentDidCatch(error, errorInfo)

第一个在抛出错误后触发,第二个在状态更新后触发。

显示组件

修改 App.jsx 内容如下:

import React, { Suspense } from "react";
import UserList from "./UserList";
import ErrorBoundary from "./ErrorBoundary";

const FallbackLoader = () => (
  <div className="p-10 text-center font-bold">正在加载用户...</div>
);

function App() {
  return (
    <div className="App bg bg-gray-200 h-screen w-screen flex flex-col items-center justify-center px-5">
      <main>
        <ErrorBoundary>
          <Suspense fallback={<FallbackLoader />}>
            <UserList />
          </Suspense>
        </ErrorBoundary>
      </main>
    </div>
  );
}
export default App;

FallbackLoader 组件提供加载时的占位 UI。 在 App 中,我们用 Suspense 包裹 <UserList/>,并传入 FallbackLoader 作为 fallback,再外层加上 ErrorBoundary 处理错误。

运行 npm run dev 即可看到效果。

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/cd988c3022654b829383ecbf4704fb73~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5o6Y6YeR5a6J5Lic5bC8:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTUyMTM3OTgyMzM0MDc5MiJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1755847551&x-orig-sign=cQtJcOeAZmZiF9VBCjheQDXNV04%3D

接下来,我们给应用加上 暗黑模式


use vs useContext

为了展示 use API 如何超越 useContext,我们来实现暗黑模式。 在 use 出现之前,如果要实现暗黑模式,你大概率会用 useContext,例如:

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

然后通过 useContext 访问:

const ThemeToggle = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme}>
      切换到 {theme === 'light' ? '深色' : '浅色'} 模式
    </button>
  );
};

而使用 use,写法更简洁。

使用 use 实现暗黑模式

创建 ThemeContext.jsx 并写入:

import React, { createContext, useState, useEffect } from "react";
const ThemeContext = createContext(null);

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState(() => {
    const savedTheme = localStorage.getItem("theme");
    return savedTheme || "light";
  });

  useEffect(() => {
    document.body.setAttribute("data-theme", theme);
    localStorage.setItem("theme", theme);
  }, [theme]);

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
export default ThemeContext;

然后在 UserList.jsx 中引入:

import ThemeContext from './contexts/ThemeContext';

并通过 use 来消费 context:

const { theme } = use(ThemeContext);

更新后的 UserList.jsx

import React, { use } from 'react';
import { getUsersPromise } from './api';
import ThemeContext from './contexts/ThemeContext';

function UserList() {
  const { theme } = use(ThemeContext);
  const users = use(getUsersPromise());

  return (
    <div>
      <h1 className='text-center py-5 text-2xl font-bold text-gray-900 dark:text-gray-100 transition-colors duration-300'>
        用户列表
      </h1>
      <ul className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5'>
        {users.map((user) => (
          <li
            key={user.id}
            className='py-10 text-center rounded-[5px] shadow-md bg-gray-500 text-gray-200 dark:bg-gray-700 dark:text-gray-100 transition-colors duration-300 ease-in-out'
          >
            <strong>{user.name}</strong> - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

接着在 App.jsx 中引入并实现 ThemeToggle,完整内容这里省略。

运行后,你将看到用户列表支持明暗模式切换。


use 的局限性

虽然 use 很强大,但仍有一些限制:

  • Promise 必须稳定:传递给 use 的 Promise 必须被缓存,否则会导致无限循环。

例如把 getUsersPromise 改成直接返回 fetchUsers()

export function getUsersPromise() {
  return fetchUsers();
}

运行后你会发现网络请求无限触发。 这是因为每次渲染都会生成新的 Promise,从而再次触发 Suspense,导致组件不断重新挂起。

因此,推荐始终搭配 Suspense + ErrorBoundary 来使用,以保证良好的用户体验。


总结

一句话总结: use() API 是 React 的一次重大飞跃,尤其在服务端优先(server-first)应用中,它能替代很多 useContext 的场景,并带来更强的异步友好能力。

在本教程中,我们体验了 use() 的应用:解包 Promise、结合 Suspense 与错误边界,以及无需样板代码即可实现主题切换。

有了 use(),状态管理和数据获取更简洁、更直观,也让现代 React 应用的开发变得更高效、顺滑。