<

1.React 19 新特性 #

升级准备

安装新版本

Codemods 工具

破坏性变更

错误处理改进

移除已弃用 API

npm create vite@latest
npm install
npm install --save-exact react@rc react-dom@rc
npm i express cors
npm run dev

2.use #

use 允许你直接在组件中处理异步操作(例如 Promise 或异步函数),而无需显式管理状态、useEffect 或手动 .then/.catch

它的目标是让组件更加声明式,直接表达“使用这个异步资源”而无需处理数据获取和状态管理的样板代码。

2.1 use 的核心特性 #

  1. 简化异步数据获取

    • 将异步资源直接作为 use 的参数。
    • 自动挂起(suspend)组件,直到资源加载完成。
  2. 内置与 Suspense 集成

    • 在加载数据时,自动触发 Suspense fallback(如加载指示器)。
    • 不需要显式地处理 loading 状态。

2.2 use 的语法 #

const data = use(fetchData());

2.3 use 的优势 #

  1. 更简单的代码

    • 不再需要 useStateuseEffect 手动管理异步状态。
    • 避免了常见的异步问题,如竞态条件或错误处理的疏漏。
  2. 更好的可读性

    • 声明式获取数据,逻辑更直观,紧凑明了。
  3. 自动与 Suspense 配合

    • 内置挂起机制,自动管理 loading 和 error 状态,减少样板代码。

对比传统方式 | 特性| 传统异步方式| use 使用方式| |----|----|----| | 数据获取| 需要 useEffect + useState 手动管理 | 直接 use(fetchData())| | 错误处理| 需要手动捕获错误并更新状态| 自动触发 Suspense error boundary| | Loading 状态管理| 需要显式设置 isLoading 状态| 自动由 Suspense fallback 管理| | 数据流和逻辑耦合| 逻辑分散在多个 Hook 中| 数据获取逻辑集中在一个语句|

2.4 use 案例 #

2.4.1 App1.jsx #

src\apps\App1.jsx

// 从 React 中导入 use hook
import { use } from "react";
// 创建一个立即解析为 'hello' 的 Promise
const getMessage = Promise.resolve("hello");
// 定义 App 组件
function App() {
  // 使用 use hook 处理 Promise 并获取数据
  const data = use(getMessage);
  // 返回包含数据的 JSX
  return <div>{data}</div>;
}
// 导出 App 组件
export default App;

2.5 use 原理 #

src\apps\App2.jsx

//import { use } from 'react';
// 创建一个立即解析为 'hello' 的 Promise
const getMessage = Promise.resolve("hello");
// 定义 App 组件
function App() {
  console.log("App 组件被渲染");
  // 使用 use hook 处理 Promise 并获取数据
  const data = use(getMessage);
  // 返回包含数据的 JSX
  return <div>{data}</div>;
}
// 导出 App 组件
export default App;
function use(promise) {
  // 根据 promise 的状态进行不同处理
  switch (promise.status) {
    // 如果 promise 已完成,返回结果
    case "fulfilled":
      return promise.value;
    // 如果 promise 已拒绝,抛出错误
    case "rejected":
      throw promise.reason;
    // 默认情况下处理 pending 状态
    default:
      throw promise.then(
        // 成功回调:设置状态为完成并保存结果
        (value) => {
          promise.status = "fulfilled";
          promise.value = value;
        },
        // 失败回调:设置状态为拒绝并保存错误原因
        (reason) => {
          promise.status = "rejected";
          promise.reason = reason;
        }
      );
  }
}

2.6 条件渲染 #

src\apps\App10.jsx

// 从 React 中导入 use hook
import { use } from "react";
// 创建一个立即解析为 'hello' 的 Promise
const getMessage = Promise.resolve("hello");
// 创建一个立即解析为 'world' 的 Promise
const getAnotherMessage = Promise.resolve("world");
function App() {
  // 定义一个布尔值条件
  const condition = false;
  // 根据条件使用不同的 Promise
  const data = condition ? use(getMessage) : use(getAnotherMessage);
  // 返回包含条件渲染结果的 JSX
  return (
    <div>
      <p>条件渲染的结果: {data}</p>
      {/* 当 condition 为 true 时才渲染此段落 */}
      {condition && <p>条件为真时显示</p>}
    </div>
  );
}
export default App;

2.7 循环 #

src\apps\App11.jsx

// 从 React 中导入 use hook
import { use } from "react";
// 创建一个包含三个立即解析的 Promise 的数组
const messages = [
  Promise.resolve("hello"),
  Promise.resolve("world"),
  Promise.resolve("react"),
];
// 定义主应用组件
function App() {
  // 使用 map 遍历 messages 数组,处理每个 Promise
  const dataList = messages.map((promise, index) => {
    // 使用 use hook 获取 Promise 的结果
    const data = use(promise);
    // 返回包含消息的 JSX 元素
    return (
      <div key={index}>
        <p>
          消息 {index + 1}: {data}
        </p>
      </div>
    );
  });
  // 返回包含所有消息的容器
  return <div>{dataList}</div>;
}
// 导出 App 组件
export default App;

3. ErrorBoundary #

src\apps\App3.jsx

// 从 React 中导入 Component 类
import { Component } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义错误边界组件
class ErrorBoundary extends Component {
  // 构造函数,初始化状态
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  // 静态方法,用于从错误中派生新状态
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  // 渲染方法
  render() {
    // 如果有错误则显示错误信息
    if (this.state.hasError) {
      return <div>出错了: {this.state.error}</div>;
    }
    // 否则渲染子组件
    return this.props.children;
  }
}
// 为 ErrorBoundary 组件定义 PropTypes
ErrorBoundary.propTypes = {
  children: PropTypes.node.isRequired,
};
// 创建一个会被拒绝的 Promise
const getMessage = Promise.reject("something went wrong");
// 定义主应用组件
function App() {
  // 打印渲染信息
  console.log("App 组件被渲染");
  // 返回被 ErrorBoundary 包裹的 Message 组件
  return (
    <ErrorBoundary>
      <Message />
    </ErrorBoundary>
  );
}
// 定义 Message 组件
function Message() {
  // 使用 use hook 处理 Promise
  const data = use(getMessage);
  // 返回数据显示
  return <div>{data}</div>;
}
// 导出 App 组件
export default App;
// 实现 use 函数
function use(promise) {
  // 根据 promise 的状态进行不同处理
  switch (promise.status) {
    // 如果 promise 已完成,返回结果
    case "fulfilled":
      return promise.value;
    // 如果 promise 已拒绝,抛出错误
    case "rejected":
      throw promise.reason;
    // 默认情况下处理 pending 状态
    default:
      throw promise.then(
        // 成功回调:设置状态为完成并保存结果
        (value) => {
          promise.status = "fulfilled";
          promise.value = value;
        },
        // 失败回调:设置状态为拒绝并保存错误原因
        (reason) => {
          promise.status = "rejected";
          promise.reason = reason;
        }
      );
  }
}

4. React 的严格模式 #

在 React 的严格模式下,组件首次加载时会执行两次,这一行为是有意设计的,旨在帮助开发者发现潜在的错误和不纯的组件。

  1. 严格模式的目的 严格模式(Strict Mode) 是 React 提供的一种开发工具,用于识别不安全的生命周期方法、过时的 API 以及其他潜在的问题。它通过额外的检查来确保组件是纯函数,即给定相同的输入总是返回相同的输出。

  2. 双重渲染的实现 在严格模式下,React 会对每个组件进行双重渲染。这意味着在开发环境中,组件函数会被调用两次。这种设计有几个目的:

  3. 检测副作用:通过双重渲染,开发者可以更容易地发现组件在渲染过程中是否存在副作用(例如,修改外部状态)。

  4. 验证清理逻辑:如果组件使用了 useEffect 钩子,React 会在第一次渲染时调用该钩子,并在第二次渲染时再次调用,以确保清理逻辑能够正常工作。

src\apps\App4.jsx

// 导入 React 的 hooks
import { useEffect, useRef, useState } from "react";
// 定义 StrictMode 组件
function StrictMode() {
  // 定义计数状态
  const [count, setCount] = useState(0);
  // 使用 ref 记录组件是否已加载
  const hasLoaded = useRef(false);
  // 使用 ref 记录组件渲染次数
  const renderCount = useRef(0);
  // 每次渲染时打印渲染次数
  console.log(`组件渲染次数: ${++renderCount.current}`);
  // 使用 useEffect 处理副作用
  useEffect(() => {
    // 打印 effect 执行次数
    console.log(`Effect执行次数: ${count}`);
    // 仅在首次加载时执行
    if (!hasLoaded.current) {
      console.log("仅首次加载执行一次");
      hasLoaded.current = true;
    }
    // 返回清理函数
    return () => {
      console.log("Effect清理");
    };
  }, [count]);
  // 渲染 UI
  return (
    <div>
      {/* 显示当前计数 */}
      <p>当前计数: {count}</p>
      {/* 点击按钮增加计数 */}
      <button onClick={() => setCount((c) => c + 1)}>增加</button>
    </div>
  );
}
// 导出组件
export default StrictMode;

5. Suspense #

Suspense 是 React 的一个强大的工具,用于处理异步操作,优化用户界面加载体验。它通过挂起组件渲染,直到数据或资源准备就绪,从而提升用户体验,尤其是在加载动态数据或代码拆分时。

5.1 核心概念 #

Suspense 的工作机制是挂起组件的渲染,等待某些异步资源(如数据或代码)加载完成。加载期间,React 会渲染一个 fallback 指定的备用 UI。

5.2 语法: #

<Suspense fallback={<LoadingComponent />}>
  <YourComponent />
</Suspense>

5.3 代码拆分 #

使用动态导入(React.lazy)按需加载组件。

5.3.1 App5.jsx #

src\apps\App5.jsx

// 从 React 中导入 Suspense 组件
import React, { Suspense } from "react";
// 使用 React.lazy 动态导入 LazyComponent 组件
const LazyComponent = React.lazy(() => import("../components/LazyComponent"));
// 定义主应用组件
function App() {
  return (
    // 使用 Suspense 包裹懒加载组件,并提供加载中的提示
    <Suspense fallback={<p>Loading component...</p>}>
      {/* 渲染懒加载的组件 */}
      <LazyComponent />
    </Suspense>
  );
}
// 导出 App 组件
export default App;

5.3.2 LazyComponent.jsx #

src\components\LazyComponent.jsx

function LazyComponent() {
  return (
    <div>
      <h1>这是一个延迟加载的组件</h1>
    </div>
  );
}
export default LazyComponent;

5.4 图片或资源加载 #

使用 Suspense 等待图片或其他资源加载。

src\apps\App6.jsx

// 导入React的Suspense组件用于处理异步加载
import { Suspense } from "react";
// 导入PropTypes用于类型检查
import PropTypes from "prop-types";
// 图片加载组件,接收src属性
function ImageLoader({ src }) {
  // 预加载图片并获取资源
  const resource = preloadImage(src);
  // 渲染图片,通过read()方法获取加载后的src
  return <img src={resource.read()} alt="Loaded" />;
}
// 定义ImageLoader组件的属性类型
ImageLoader.propTypes = {
  src: PropTypes.string.isRequired,
};
// 创建图片缓存Map
const imageCache = new Map();
// 图片预加载函数
function preloadImage(src) {
  // 如果图片已在缓存中,直接返回缓存的资源
  if (imageCache.has(src)) {
    return imageCache.get(src);
  }
  // 初始化状态和结果变量
  let status = "pending";
  let result;
  // 创建Promise来处理图片加载
  const promise = new Promise((resolve, reject) => {
    // 创建新的Image对象
    const img = new Image();
    img.src = src;
    // 图片加载成功的处理函数
    img.onload = () => {
      status = "success";
      result = src;
      resolve(src);
    };
    // 图片加载失败的处理函数
    img.onerror = () => {
      status = "error";
      result = new Error("Failed to load image");
      reject(result);
    };
  });
  // 创建资源对象
  const resource = {
    // read方法根据状态返回不同结果
    read() {
      if (status === "pending") throw promise;
      if (status === "error") throw result;
      return result;
    },
  };
  // 将资源存入缓存
  imageCache.set(src, resource);
  return resource;
}
// 主应用组件
function App() {
  return (
    // 使用Suspense包裹异步组件,提供加载中的提示
    <Suspense fallback={<p>Loading image...</p>}>
      <ImageLoader src="https://www.baidu.com/img/flexible/logo/pc/result.png" />
    </Suspense>
  );
}
// 导出App组件
export default App;

5.5 数据获取 #

React 引入了对数据加载的 Suspense 支持,通过挂起未完成的异步数据加载,让界面更直观地处理异步状态。

5.5.1 App7.jsx #

src\apps\App7.jsx

// 导入 React 的 use 和 Suspense 组件
import { use, Suspense } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义异步函数用于获取用户数据
async function fetchUserData(userId) {
  // 发送 GET 请求到本地服务器获取用户数据
  const response = await fetch(`http://localhost:3000/user/${userId}`);
  // 将响应转换为 JSON 格式并返回
  return response.json();
}
// 定义 UserProfile 组件,接收 user 属性
function UserProfile({ user }) {
  // 使用 use hook 处理异步数据
  const userData = use(user);
  // 返回包含用户信息的 JSX
  return (
    <div>
      <h1>{userData.name}</h1>
      <p>{userData.email}</p>
    </div>
  );
}
// 为 UserProfile 组件定义 PropTypes
UserProfile.propTypes = {
  user: PropTypes.object.isRequired,
};
// 定义主应用组件
function App() {
  // 调用 fetchUserData 获取用户数据
  const user = fetchUserData(123);
  // 返回包含在 Suspense 中的 UserProfile 组件
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <UserProfile user={user} />
    </Suspense>
  );
}
// 导出 App 组件
export default App;

5.5.2 server.cjs #

server.cjs

const express = require("express");
const cors = require("cors");
const app = express();

app.use(cors());

app.get("/user/:userId", (req, res) => {
  const userId = req.params.userId;
  res.json({ userId, name: "mary", email: "mary@qq.com" });
});

app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

错误不会自动处理,需要结合 ErrorBoundary 使用:

<ErrorBoundary fallback={<p>Error occurred!</p>}>
  <Suspense fallback={<p>Loading...</p>}>
    <YourComponent />
  </Suspense>
</ErrorBoundary>

5.6 Suspense 工作原理 #

Suspense 是 React 提供的用于管理异步数据加载的机制。它通过挂起(suspending)组件的渲染,直到其依赖的资源(例如数据或动态导入的代码)加载完成,同时显示 fallback 指定的备用 UI。

核心流程

  1. 检测挂起状态

    • 当组件中使用了异步资源(如 Promise),React 检测到这些资源未就绪时,会触发挂起。
    • Suspense 会中断组件的渲染,等待异步任务完成。
  2. 触发挂起

    • 组件内的异步操作(如 Promise)会抛出一个挂起状态,通知 React 当前任务尚未完成。
    • React 捕获挂起状态,停止渲染流程。
  3. 显示备用 UI

    • 在挂起期间,React 渲染最靠近的 Suspense 组件的 fallback
    • 这通常是一个加载指示器或占位符。
  4. 异步任务完成

    • 当异步任务完成(Promise resolved),组件重新开始渲染,并用实际内容替换 fallback

5.7 多个挂起任务 #

Suspense 支持同时管理多个挂起的 Promise

5.7.1 App8.jsx #

src\apps\App8.jsx

// 导入 React 的 use 和 Suspense 组件
import { use, Suspense } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义一个模拟异步数据获取的函数,接收延迟时间参数
function fetchData(ms) {
  // 返回一个 Promise,在指定时间后解析为字符串
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(ms.toString());
    }, ms);
  });
}
// 定义第一个异步组件,接收 data 属性
function AsyncComponent({ data }) {
  // 使用 use hook 处理异步数据
  const result = use(data);
  // 返回包含结果的 JSX
  return <div>AsyncComponent {result}</div>;
}
// 为 AsyncComponent 定义 PropTypes
AsyncComponent.propTypes = {
  data: PropTypes.string.isRequired,
};
// 定义第二个异步组件,接收 data 属性
function AnotherAsyncComponent({ data }) {
  // 使用 use hook 处理异步数据
  const result = use(data);
  // 记录计时器结束时间
  console.timeEnd("App");
  // 返回包含结果的 JSX
  return <div>AnotherAsyncComponent {result}</div>;
}
// 为 AnotherAsyncComponent 定义 PropTypes
AnotherAsyncComponent.propTypes = {
  data: PropTypes.string.isRequired,
};
// 定义主应用组件
function App() {
  // 开始计时
  console.time("App");
  // 创建两个延迟不同的异步数据请求
  const data1 = fetchData(1000);
  const data2 = fetchData(5000);
  // 返回包含在 Suspense 中的两个异步组件
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <AsyncComponent data={data1} />
      <AnotherAsyncComponent data={data2} />
    </Suspense>
  );
}
// 导出 App 组件
export default App;

5.8 嵌套的 Suspense #

Suspense 组件可以嵌套使用,管理不同子树的挂起状态:

src\apps\App9.jsx

import { use, Suspense } from "react";
import PropTypes from "prop-types";
function fetchData(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(ms.toString());
    }, ms);
  });
}
function LoadingA() {
  return <div>加载组件A中...</div>;
}
function LoadingB() {
  return <div>加载组件B中...</div>;
}
function ComponentA({ data }) {
  const result = use(data);
  return <div>组件A加载完成: {result}</div>;
}
ComponentA.propTypes = {
  data: PropTypes.string.isRequired,
};
function ComponentB({ data }) {
  const result = use(data);
  return <div>组件B加载完成: {result}</div>;
}
ComponentB.propTypes = {
  data: PropTypes.string.isRequired,
};
function App() {
  const data1 = fetchData(1000);
  const data2 = fetchData(3000);
  return (
    <Suspense fallback={<LoadingA />}>
      <ComponentA data={data1} />
      <Suspense fallback={<LoadingB />}>
        <ComponentB data={data2} />
      </Suspense>
    </Suspense>
  );
}
export default App;

6. 案例 #

6.1 数据更新 #

src\apps\App12.jsx

// 从 React 中导入必要的 hooks 和组件
import { use, Suspense, useState } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 创建一个返回 Promise 的工具函数,接收值和延迟时间作为参数
const createPromise = (value, delay) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), delay);
  });
};
// 显示数据的组件,接收一个 promise 作为 props
function DisplayData({ promise }) {
  // 使用 use hook 来处理 Promise
  const data = use(promise);
  return <div>当前数据: {data}</div>;
}
// 为 DisplayData 组件定义 PropTypes
DisplayData.propTypes = {
  promise: PropTypes.string.isRequired,
};
// 主组件
function Use() {
  // 使用 useState 初始化一个 promise 状态,初始值是一个延迟 1 秒的 Promise
  const [promise, setPromise] = useState(() => createPromise("初始数据", 1000));
  return (
    <div>
      <h2>数据更新演示</h2>
      {/* 使用 Suspense 包裹异步组件,提供加载状态 */}
      <Suspense fallback={<div>加载中...</div>}>
        <DisplayData promise={promise} />
      </Suspense>
      {/* 点击按钮时创建新的 Promise 并更新状态 */}
      <button
        onClick={() => {
          setPromise(createPromise("更新的数据", 1000));
        }}
      >
        更新数据
      </button>
    </div>
  );
}
// 导出主组件
export default Use;

6.2 添加数据项 #

src\apps\App13.jsx

// 从 React 中导入必要的 hooks 和组件
import { use, Suspense, useState } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 创建一个返回 Promise 的工具函数,接收列表数据作为参数
const createPromise = (list) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(list), 1000);
  });
};
// 显示单个列表项的组件,接收一个 promise 作为 props
function ItemDisplay({ promise }) {
  // 使用 use hook 处理 Promise
  const item = use(promise);
  return <li>{item}</li>;
}
// 为 ItemDisplay 组件定义 PropTypes
ItemDisplay.propTypes = {
  promise: PropTypes.string.isRequired,
};
// 显示整个列表的组件,接收一个 promises 数组作为 props
function ListDisplay({ promises }) {
  return (
    <ul>
      {promises.map((promise, index) => (
        // 为每个列表项添加 Suspense 包装
        <Suspense key={index} fallback={<li>加载中...</li>}>
          <ItemDisplay promise={promise} />
        </Suspense>
      ))}
    </ul>
  );
}
// 为 ListDisplay 组件定义 PropTypes
ListDisplay.propTypes = {
  promises: PropTypes.arrayOf(PropTypes.string).isRequired,
};
// 主应用组件
function App() {
  // 使用 useState 初始化 promises 数组,初始值包含一个 Promise
  const [promises, setPromises] = useState(() => [createPromise("数据 1")]);
  return (
    <div>
      <h2>列表数据演示</h2>
      <ListDisplay promises={promises} />
      {/* 点击按钮时创建新的 Promise 并添加到列表中 */}
      <button
        onClick={() => {
          const newPromise = createPromise(`数据 ${promises.length + 1}`);
          setPromises([...promises, newPromise]);
        }}
      >
        添加数据
      </button>
    </div>
  );
}
// 导出主组件
export default App;

6.3 加载下一页 #

src\apps\App14.jsx

// 从 React 中导入必要的 hooks 和组件
import { use, Suspense, useState } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 模拟获取分页数据的函数,接收页码作为参数
const fetchPageData = (page) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      // 创建一个包含5个数据项的数组
      const pageData = Array.from(
        { length: 5 },
        (_, i) => `第${page}页的数据项 ${i + 1}`
      );
      resolve(pageData);
    }, 1000);
  });
};
// 显示单页数据的组件,接收一个 promise 作为 props
function PageData({ promise }) {
  // 使用 use hook 处理 Promise
  const data = use(promise);
  return (
    <div>
      {/* 遍历数据数组并渲染每一项 */}
      {data.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
}
// 为 PageData 组件定义 PropTypes
PageData.propTypes = {
  promise: PropTypes.string.isRequired,
};
// 主应用组件
function Use() {
  // 使用 useState 初始化页面数据数组,初始值为第一页的数据
  const [pages, setPages] = useState(() => [fetchPageData(1)]);
  // 使用 useState 跟踪当前页码
  const [currentPage, setCurrentPage] = useState(1);
  return (
    <div>
      <h2>分页数据加载演示</h2>
      {/* 遍历并渲染所有页面的数据 */}
      {pages.map((pagePromise, index) => (
        <Suspense key={index} fallback={<div>正在加载第{index + 1}页...</div>}>
          <PageData promise={pagePromise} />
        </Suspense>
      ))}
      {/* 点击按钮加载下一页数据 */}
      <button
        onClick={() => {
          const nextPage = currentPage + 1;
          setCurrentPage(nextPage);
          setPages([...pages, fetchPageData(nextPage)]);
        }}
      >
        加载更多
      </button>
    </div>
  );
}
// 导出主组件
export default Use;

6.4 搜索建议 #

src\apps\App15.jsx

// 从 React 中导入必要的 hooks 和组件
import { use, Suspense, useState } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义一个获取搜索数据的函数,接收关键字作为参数
const fetchSearchData = (keyword) => {
  // 创建一个 AbortController 实例用于取消请求
  const controller = new AbortController();
  // 发起带有 abort 信号的 fetch 请求
  const promise = fetch(`http://localhost:3000/search?keyword=${keyword}`, {
    signal: controller.signal,
  })
    // 将响应转换为 JSON
    .then((res) => res.json())
    // 如果请求被取消,返回空数组
    .catch(() => []);
  // 将 controller 附加到 promise 上以便后续使用
  promise.controller = controller;
  return promise;
};
// 显示搜索结果的组件,接收一个 promise 作为 props
function SearchResults({ promise }) {
  // 使用 use hook 处理 Promise
  const results = use(promise);
  return (
    <ul>
      {/* 遍历搜索结果并渲染每一项 */}
      {results.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}
// 为 SearchResults 组件定义 PropTypes
SearchResults.propTypes = {
  promise: PropTypes.string.isRequired,
};
// 主应用组件
function Use() {
  // 使用 useState 管理搜索 Promise 和关键字
  const [searchPromise, setSearchPromise] = useState(null);
  const [keyword, setKeyword] = useState("");
  // 处理搜索的函数
  const handleSearch = (value) => {
    // 如果存在之前的搜索请求,则取消它
    if (searchPromise?.controller) {
      searchPromise.controller.abort();
    }
    // 更新关键字状态
    setKeyword(value);
    // 如果输入值不为空,则发起新的搜索请求
    setSearchPromise(value.trim() ? fetchSearchData(value) : null);
  };
  return (
    <div>
      <h2>搜索建议</h2>
      {/* 搜索输入框 */}
      <input
        type="text"
        value={keyword}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="请输入关键字"
      />
      {/* 当存在搜索请求时显示结果 */}
      {searchPromise && (
        <Suspense fallback={<div>正在搜索{keyword}...</div>}>
          <SearchResults promise={searchPromise} />
        </Suspense>
      )}
    </div>
  );
}
// 导出主组件
export default Use;
const express = require("express");
const cors = require("cors");
const app = express();

app.use(cors());

app.get("/user/:userId", (req, res) => {
  const userId = req.params.userId;
  res.json({ userId, name: "mary", email: "mary@qq.com" });
});
app.get("/search", (req, res) => {
  const keyword = req.query.keyword;
  setTimeout(() => {
    const results = Array.from({ length: 10 }, (_, i) => `${keyword}${i + 1}`);
    res.json(results);
  }, 1000);
});
app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

7. ref #

useRef 是 React 提供的一个 Hook,用于管理不参与组件渲染流程的可变引用(ref)。它非常轻量级且强大,适合在某些场景下替代 state,尤其是当数据的变化不需要触发组件重新渲染时。

useImperativeHandle 是 React 提供的一个高级 Hook,用于自定义通过 ref 暴露给父组件的实例值或方法。让父组件可以通过 ref 访问子组件中自定义的值或方法,而不仅仅是 DOM 节点。

7.1 获得焦点 #

src\apps\App16.jsx

// 导入 React 的 useRef 和 useImperativeHandle hooks
import { useRef, useImperativeHandle } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义子组件 ChildInput,接收 ref 参数
function ChildInput({ ref }) {
  // 创建一个 ref 用于引用 input 元素
  const inputRef = useRef();
  // 使用 useImperativeHandle 自定义暴露给父组件的方法
  useImperativeHandle(
    ref,
    () => {
      return {
        // 聚焦方法
        focus: () => {
          inputRef.current.focus();
        },
        // 获取输入值方法
        getValue: () => {
          return inputRef.current.value;
        },
        // 设置输入值方法
        setValue: (value) => {
          inputRef.current.value = value;
        },
      };
    },
    []
  );

  // 渲染带 ref 的 input 元素
  return <input ref={inputRef} />;
}
// 为 ChildInput 组件添加 PropTypes 类型检查
ChildInput.propTypes = {
  ref: PropTypes.object,
};
// 定义父组件 Use
function Use() {
  // 创建一个 ref 用于引用子组件
  const childRef = useRef();

  // 点击按钮时的处理函数
  const handleClick = () => {
    // 调用子组件的 focus 方法
    childRef.current.focus();
    // 调用子组件的 setValue 方法设置值
    childRef.current.setValue("你好 React 19!");
    // 调用子组件的 getValue 方法并打印值
    console.log("当前输入值:", childRef.current.getValue());
  };
  // 渲染组件
  return (
    <div>
      <h3>Ref 传递示例</h3>
      <ChildInput ref={childRef} />
      <button onClick={handleClick}>设置输入框</button>
    </div>
  );
}
// 导出 Use 组件
export default Use;

7.2 提示框 #

src\apps\App17.jsx

// 导入 React 的 useRef 和 useImperativeHandle hooks
import { useRef, useImperativeHandle } from "react";
// 导入 PropTypes 用于类型检查
import PropTypes from "prop-types";
// 定义 Tooltip 组件,接收 ref 参数
const Tooltip = ({ ref }) => {
  // 创建一个 ref 用于引用提示框元素
  const tooltipRef = useRef();
  // 使用 useImperativeHandle 自定义暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    // 显示提示框的方法
    show: (event) => {
      // 获取鼠标点击位置的坐标
      const x = event.clientX;
      const y = event.clientY;
      // 设置提示框显示并定位
      tooltipRef.current.style.display = "block";
      tooltipRef.current.style.left = `${x + 10}px`;
      tooltipRef.current.style.top = `${y + 10}px`;
      // 设置初始透明度为0
      tooltipRef.current.style.opacity = "0";
      // 触发重排以确保过渡动画生效
      tooltipRef.current.offsetHeight;
      // 设置透明度为1,触发淡入动画
      tooltipRef.current.style.opacity = "1";
    },
    // 隐藏提示框的方法
    hide: () => {
      // 设置透明度为0,触发淡出动画
      tooltipRef.current.style.opacity = "0";
      // 2秒后隐藏提示框
      setTimeout(() => {
        tooltipRef.current.style.display = "none";
      }, 2000);
    },
  }));
  // 渲染提示框元素
  return (
    <div
      ref={tooltipRef}
      style={{
        display: "none",
        position: "fixed",
        backgroundColor: "#333",
        color: "white",
        padding: "8px 12px",
        borderRadius: "4px",
        fontSize: "14px",
        transition: "opacity 2s ease",
        zIndex: 1000,
      }}
    >
      这是一个提示框
    </div>
  );
};
// 为 Tooltip 组件添加 PropTypes 类型检查
Tooltip.propTypes = {
  ref: PropTypes.object,
};
// 定义主应用组件
function App() {
  // 创建一个 ref 用于引用 Tooltip 组件
  const tooltipRef = useRef();
  // 鼠标进入时的处理函数
  const handleMouseEnter = (e) => {
    tooltipRef.current.show(e);
  };
  // 鼠标离开时的处理函数
  const handleMouseLeave = () => {
    tooltipRef.current.hide();
  };
  // 渲染主应用组件
  return (
    <div>
      <button onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
        鼠标悬停显示提示
      </button>
      <Tooltip ref={tooltipRef} />
    </div>
  );
}
// 导出主应用组件
export default App;

7.3 国际化 #

src\apps\App18.jsx

// 导入必要的React钩子
import { createContext, use, useState } from "react";
// 创建语言上下文,默认值为'zh'(中文)
const LangContext = createContext("zh");
// 定义多语言文本对象
const messages = {
  // 中文文本
  zh: {
    greeting: "你好,世界!",
    switchLang: "切换语言",
  },
  // 英文文本
  en: {
    greeting: "Hello World!",
    switchLang: "Switch Language",
  },
};
// 定义语言文本显示组件
function LanguageText() {
  // 使用上下文获取当前语言
  const lang = use(LangContext);
  return (
    <div>
      <h2>{messages[lang].greeting}</h2>
    </div>
  );
}
// 定义主应用组件
function App() {
  // 定义语言状态,默认为中文
  const [lang, setLang] = useState("zh");
  return (
    // 使用LangContext提供语言上下文
    <LangContext value={lang}>
      <div>
        {/* 渲染语言文本组件 */}
        <LanguageText />
        {/* 切换语言按钮 */}
        <button onClick={() => setLang(lang === "zh" ? "en" : "zh")}>
          {messages[lang].switchLang}
        </button>
      </div>
    </LangContext>
  );
}
// 导出主应用组件
export default App;

8. useDeferredValue #

useDeferredValue 是 React 引入的一个新 Hook,用于实现延迟更新。它可以帮助在 React 应用中优化性能,尤其是在处理高优先级和低优先级更新时提供更流畅的用户体验。 useDeferredValue 接受一个值并返回一个延迟的值(deferred value)。如果传入的值在一段时间内发生了变化,useDeferredValue 会延迟更新返回的值,优先让其他更高优先级的任务完成。

// 导入React的useState和useDeferredValue钩子
import { useState, useDeferredValue } from "react";
// 导入PropTypes用于类型检查
import PropTypes from "prop-types";
// 定义主应用组件
function App() {
  // 定义搜索查询状态
  const [query, setQuery] = useState("");
  // 使用useDeferredValue创建延迟更新的查询值
  const deferredQuery = useDeferredValue(query);
  // 使用延迟查询值执行昂贵的搜索操作
  const searchResults = expensiveSearchFunction(deferredQuery);
  return (
    <div>
      {/* 搜索输入框 */}
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {/* 渲染搜索结果组件 */}
      <SearchResults results={searchResults} />
    </div>
  );
}
// 模拟耗时的搜索函数
function expensiveSearchFunction(query) {
  console.log("Computing search results for:", query);
  // 创建20000个模拟搜索结果
  return Array(20000)
    .fill(0)
    .map((_, idx) => `Result ${idx} for "${query}"`);
}
// 定义SearchResults组件的属性类型
SearchResults.propTypes = {
  results: PropTypes.array.isRequired,
};
// 定义搜索结果显示组件
function SearchResults({ results }) {
  return (
    <ul>
      {/* 遍历并渲染所有搜索结果 */}
      {results.map((result, index) => (
        <li key={index}>{result}</li>
      ))}
    </ul>
  );
}
// 导出主应用组件
export default App;

9. useTransition #

useTransition 是 React 引入的一个新 Hook,用于控制更新优先级,允许将某些更新标记为“过渡任务”(Transition)。通过将任务分为高优先级和低优先级,可以提升用户体验,尤其是在处理复杂、耗时的更新时。

// 导入React的useState和useTransition钩子
import { useState, useTransition } from "react";
// 导入PropTypes用于类型检查
import PropTypes from "prop-types";
// 定义主应用组件
function App() {
  // 定义搜索查询状态
  const [query, setQuery] = useState("");
  // 定义搜索结果状态
  const [results, setResults] = useState([]);
  // 使用useTransition创建过渡状态和启动函数
  const [isPending, startTransition] = useTransition();
  // 处理输入变化的函数
  const handleChange = (e) => {
    // 获取输入值
    const value = e.target.value;
    // 立即更新查询状态(高优先级)
    setQuery(value);
    // 使用startTransition包装低优先级更新
    startTransition(() => {
      // 执行昂贵的搜索操作
      const searchResults = expensiveSearchFunction(value);
      // 更新搜索结果
      setResults(searchResults);
    });
  };
  // 渲染UI
  return (
    <div>
      {/* 搜索输入框 */}
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="Search..."
      />
      {/* 渲染搜索结果组件 */}
      <SearchResults results={results} isPending={isPending} />
    </div>
  );
}
// 定义SearchResults组件的属性类型
SearchResults.propTypes = {
  results: PropTypes.array.isRequired,
  isPending: PropTypes.bool.isRequired,
};
// 模拟耗时的搜索函数
function expensiveSearchFunction(query) {
  console.log("Computing search results for:", query);
  // 创建10000个模拟搜索结果
  return Array(10000)
    .fill(0)
    .map((_, idx) => `Result ${idx} for "${query}"`);
}
// 定义搜索结果显示组件
function SearchResults({ results, isPending }) {
  // 返回搜索结果列表,当isPending为true时降低透明度
  return (
    <ul style={{ opacity: isPending ? 0.5 : 1 }}>
      {/* 遍历并渲染所有搜索结果 */}
      {results.map((result, index) => (
        <li key={index}>{result}</li>
      ))}
    </ul>
  );
}
// 导出主应用组件
export default App;

10.FormAction #

FormAction 的 action 属性类似于传统 HTML 表单的 action,但它接受一个函数而不是 URL。

自动处理 FormData

简化的数据访问

const formAction = (formData) => {
  const name = formData.get("name"); // 直接通过 name 属性获取值
  const email = formData.get("email");
};

src\apps\App21.jsx

// 导入 React 的 useState 钩子
import { useState } from "react";
// 定义主应用组件
function App() {
  // 定义状态变量 message 和更新函数 setMessage
  const [message, setMessage] = useState("");
  // 定义表单提交处理函数
  const formAction = (formData) => {
    // 从表单数据中获取姓名
    const name = formData.get("name");
    // 从表单数据中获取邮箱
    const email = formData.get("email");
    // 设置成功提交消息
    setMessage(`表单提交成功! 姓名: ${name}, 邮箱: ${email}`);
  };
  // 返回组件JSX结构
  return (
    // 最外层容器
    <div>
      {/* 表单组件,设置提交处理函数 */}
      <form action={formAction}>
        {/* 姓名输入区域容器 */}
        <div>
          {/* 姓名输入标签 */}
          <label htmlFor="name">姓名:</label>
          {/* 姓名输入框 */}
          <input type="text" id="name" name="name" required />
        </div>
        {/* 邮箱输入区域容器 */}
        <div>
          {/* 邮箱输入标签 */}
          <label htmlFor="email">邮箱:</label>
          {/* 邮箱输入框 */}
          <input type="email" id="email" name="email" required />
        </div>
        {/* 提交按钮 */}
        <button type="submit">提交</button>
        {/* 第二个提交按钮,使用相同的处理函数 */}
        <button type="submit" formAction={formAction}>
          提交2
        </button>
      </form>
      {/* 显示提交后的消息 */}
      <div>{message}</div>
    </div>
  );
}
// 导出App组件
export default App;

11.useFormStatus #

useFormStatus 是 React 提供的一个钩子,用于获取表单的提交状态。它返回一个对象,其中包含 pending 属性,表示表单是否正在提交。

// 导入 React 的 useState 钩子
import { useState } from "react";
// 导入 useFormStatus 钩子用于表单状态管理
import { useFormStatus } from "react-dom";
// 定义提交按钮组件
function SubmitButton() {
  // 获取表单提交状态
  const { pending } = useFormStatus();
  // 返回按钮组件,根据pending状态显示不同文本和禁用状态
  return (
    <button type="submit" disabled={pending}>
      {pending ? "提交中..." : "提交"}
    </button>
  );
}
// 定义主应用组件
function App() {
  // 定义消息状态
  const [message, setMessage] = useState("");
  // 获取表单提交状态
  const { pending } = useFormStatus();
  // 定义异步表单提交处理函数
  async function formAction(formData) {
    // 从表单数据中获取姓名和邮箱
    const name = formData.get("name");
    const email = formData.get("email");
    try {
      // 模拟异步操作,延迟2秒
      await new Promise((resolve) => setTimeout(resolve, 2000));
      // 设置成功提交消息
      setMessage(`表单数据: 姓名: ${name}, 邮箱: ${email}`);
    } catch {
      // 设置失败提示消息
      setMessage("提交失败,请重试");
    }
  }
  // 返回组件JSX结构
  return (
    <div>
      {/* 表单组件,设置提交处理函数 */}
      <form action={formAction}>
        {/* 姓名输入区域 */}
        <div>
          <label htmlFor="name">姓名:</label>
          <input
            type="text"
            id="name"
            name="name"
            required
            disabled={pending}
          />
        </div>
        {/* 邮箱输入区域 */}
        <div>
          <label htmlFor="email">邮箱:</label>
          <input
            type="email"
            id="email"
            name="email"
            required
            disabled={pending}
          />
        </div>
        {/* 提交按钮组件 */}
        <SubmitButton />
      </form>
      {/* 显示提交消息 */}
      <div>{message}</div>
    </div>
  );
}
// 导出App组件
export default App;

12.useActionState #

useActionState 是 React 提供的一个钩子,用于在表单提交后处理状态。它返回一个对象,其中包含 dataerror 属性,分别表示表单提交后的数据和错误信息。

// 导入 React 的 useActionState Hook
import { useActionState } from "react";

// 定义 App 组件
function App() {
  // 使用 useActionState 处理表单状态和提交
  // state: 表单状态对象
  // formAction: 表单提交处理函数
  // pending: 是否正在提交
  const [state, formAction, pending] = useActionState(
    // 异步处理表单提交的函数
    async (currentState, formData) => {
      // 打印上一次的状态
      console.log("上一次状态:", currentState);
      // 获取表单数据
      const name = formData.get("name");
      const password = formData.get("password");
      // 验证密码长度
      if (!password || password.length < 6) {
        return {
          error: { message: "密码长度不能小于6位" },
          data: currentState.data,
        };
      }
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 1000));
      // 返回成功状态和数据
      return {
        error: null,
        data: {
          message: "注册成功",
          name,
          password,
          previousName: currentState.data.name,
        },
      };
    },
    // 初始状态
    { data: { name: "", previousName: "" }, error: null }
  );

  // 返回表单 JSX
  return (
    // 表单组件,绑定提交处理函数
    <form action={formAction}>
      {/* 用户名输入区域 */}
      <div>
        <label>用户名:</label>
        <input name="name" disabled={pending} />
      </div>
      {/* 密码输入区域 */}
      <div>
        <label>密码:</label>
        <input name="password" type="password" disabled={pending} />
      </div>
      {/* 提交按钮 */}
      <button type="submit" disabled={pending}>
        {pending ? "注册中..." : "注册"}
      </button>
      {/* 错误信息显示 */}
      {state.error && <div style={{ color: "red" }}>{state.error.message}</div>}
      {/* 成功信息显示 */}
      {state.data && <div style={{ color: "green" }}>{state.data.message}</div>}
    </form>
  );
}

// 导出 App 组件
export default App;

13.useOptimistic #

useOptimistic 是 React 19 中新增的一个 Hook,专门设计用于处理乐观更新的场景。乐观更新是一种优化技术,用于在用户与界面交互时,立即更新 UI,从而提供更流畅的体验,而无需等待后端的响应。

13.1 基本用法 #

useOptimistic 提供了一种声明式的方法来管理乐观状态。它的基本形式如下:

const [optimisticState, addOptimisticUpdate] = useOptimistic(
  initialState,
  reducer
);

13.2 计数器 #

// 导入必要的 React Hooks
import { useState, useOptimistic, useTransition } from "react";
// 定义计数器组件
function Counter() {
  // 使用 useState 管理计数状态
  const [count, setCount] = useState(0);
  // 使用 useTransition 处理异步状态更新
  const [isPending, startTransition] = useTransition();
  // 使用 useOptimistic 实现乐观更新
  // state 是当前状态,action 包含更新类型
  const [optimisticCount, addOptimisticUpdate] = useOptimistic(
    count,
    (state, action) => {
      switch (action.type) {
        case "increment":
          return state + 1;
        case "decrement":
          return state - 1;
        default:
          return state;
      }
    }
  );
  // 处理增加按钮点击事件
  const handleIncrement = async () => {
    // 使用 startTransition 包装异步操作
    startTransition(async () => {
      // 立即进行乐观更新
      addOptimisticUpdate({ type: "increment" });
      try {
        // 模拟异步请求,1秒后失败
        await new Promise((resolve, reject) => setTimeout(reject, 1000));
        // 更新实际计数
        setCount((count) => count + 1);
      } catch (error) {
        // 请求失败时输出错误并回滚状态
        console.error("服务器请求失败,可能需要回滚", error);
      }
    });
  };
  // 渲染组件UI
  return (
    <div>
      {/* 显示当前计数值 */}
      <p>Count: {optimisticCount}</p>
      {/* 根据加载状态显示不同内容 */}
      {isPending ? (
        <p>Loading...</p>
      ) : (
        <button onClick={handleIncrement}>+</button>
      )}
    </div>
  );
}
// 导出计数器组件
export default Counter;

13.3 待办事项 #

// 导入必要的 React Hooks
import { useState, useOptimistic, useTransition } from "react";
// 定义主应用组件
function App() {
  // 使用 useState 定义待办事项列表状态
  const [todos, setTodos] = useState([
    { id: crypto.randomUUID(), text: "学习React" },
    { id: crypto.randomUUID(), text: "学习useOptimistic" },
  ]);
  // 使用 useTransition 处理异步状态更新
  const [isPending, startTransition] = useTransition();
  // 使用 useOptimistic 实现乐观更新
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, action) => {
      switch (action.type) {
        case "add":
          return [...state, action.todo];
        case "delete":
          return state.filter((todo) => todo.id !== action.id);
        default:
          return state;
      }
    }
  );
  // 处理添加待办事项的函数
  const handleAdd = async (formData) => {
    const text = formData.get("todo").trim();
    if (!text) return;
    // 生成临时ID
    const optimisticId = crypto.randomUUID();
    // 创建临时待办事项对象
    const optimisticTodo = {
      id: optimisticId,
      text,
    };
    // 使用 startTransition 包装异步操作
    startTransition(async () => {
      // 立即进行乐观更新
      addOptimisticTodo({ type: "add", todo: optimisticTodo });
      try {
        // 模拟服务器请求,生成新ID
        const serverGeneratedId = await new Promise((resolve) =>
          setTimeout(() => resolve(crypto.randomUUID()), 1000)
        );
        // 创建实际待办事项对象
        const actualTodo = {
          id: serverGeneratedId,
          text,
        };
        // 更新实际状态
        setTodos((prev) => [...prev, actualTodo]);
      } catch (error) {
        console.error("添加失败:", error);
      }
    });
  };
  // 处理删除待办事项的函数
  const handleDelete = async (id) => {
    startTransition(async () => {
      // 立即进行乐观更新
      addOptimisticTodo({ type: "delete", id });
      try {
        // 模拟服务器请求
        await new Promise((resolve) => setTimeout(resolve, 1000));
        // 更新实际状态
        setTodos((prev) => prev.filter((todo) => todo.id !== id));
      } catch (error) {
        console.error("删除失败:", error);
      }
    });
  };
  // 渲染组件UI
  return (
    <div>
      <h1>待办事项</h1>
      {/* 空注释用于保持格式 */}
      {/* 添加待办事项的表单 */}
      <form>
        <input
          type="text"
          name="todo"
          placeholder="输入新的待办事项"
          disabled={isPending}
        />
        <button formAction={handleAdd} disabled={isPending}>
          {isPending ? "添加中..." : "添加"}
        </button>
      </form>
      <ul>
        {optimisticTodos.map((todo) => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => handleDelete(todo.id)} disabled={isPending}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
// 导出 App 组件
export default App;

14. React Compiler #

React Compiler 是 React 团队推出的一个新编译器,旨在构建阶段自动优化 React 应用程序。它能够理解 React 的规则,并在不需要重写代码的情况下,对组件和钩子中的值或值组进行自动记忆化处理,从而减少不必要的重新计算,提升应用性能。

主要功能:

14.1 安装方法 #

React Compiler 当前处于 Beta 阶段,可在 React 17 及以上版本的应用程序和库中使用。安装方式如下:

npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta

或使用 Yarn:

yarn add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta

14.2 react-compiler-healthcheck #

React Compiler Healthcheck 是一个工具,用于帮助开发者分析代码库是否符合 React Compiler(如 React Forget 编译器)的优化要求,以及检测可能影响自动化优化的代码问题。它的目标是提升代码质量并确保编译器可以正确处理代码,最大化 React 应用的性能潜力。

npx react-compiler-healthcheck
Successfully compiled 54 out of 55 components.
StrictMode usage found.
Found no usage of incompatible libraries.

14.3 eslint-plugin-react-compiler #

eslint-plugin-react-compiler 是 React Compiler 的 ESLint 插件,用于在代码审查和开发过程中检测和修复不符合 React Compiler 规则的问题。

import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
+import reactCompiler from 'eslint-plugin-react-compiler'
export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    settings: { react: { version: '18.3' } },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
+     'react-compiler': reactCompiler,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      ...reactHooks.configs.recommended.rules,
      'react/jsx-no-target-blank': 'off',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
+     'react-compiler/react-compiler': 'error',
    },
  },
]

14.4 babel-plugin-react-compiler #

babel-plugin-react-compiler 是 React Compiler 的 Babel 插件,用于在构建过程中自动优化 React 应用程序。

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+const ReactCompilerConfig = {};
export default defineConfig({
  plugins: [react({
+   babel: {
+     plugins: [
+       ["babel-plugin-react-compiler", ReactCompilerConfig],
+     ],
+   },
  })],
})

14.5 React Compiler #

14.5.1 缓存 JSX #

/* function App() {
    return <div>Hello World</div>;
} */
// 方式1:利用组件实例
function _c(size) {
  // 获取当前组件实例
  const instance = React.getCurrentInstance();

  // 如果实例上没有缓存,则创建新的缓存
  if (!instance._cache) {
    instance._cache = new Array(size);
    instance._cache.fill(Symbol.for("react.memo_cache_sentinel"));
  }

  return instance._cache;
}

// 方式2:利用闭包
function Component() {
  // 缓存数组只在组件首次渲染时创建
  const cache = React.useMemo(() => {
    const arr = new Array(1);
    arr.fill(Symbol.for("react.memo_cache_sentinel"));
    return arr;
  }, []);

  // 后续渲染复用同一个cache
  const $ = cache;
}
function App() {
  // 使用 React Compiler 初始化缓存,分配一个大小为 1 的缓存数组
  const cache = _c(1);
  // 定义用于存储 JSX 元素的变量
  let jsxElement;
  // 检查缓存的第一个位置是否是哨兵值,表示未缓存
  if (cache[0] === Symbol.for("react.memo_cache_sentinel")) {
    // 如果是哨兵值,创建新的 JSX 元素
    jsxElement = <div>Hello World</div>;
    // 将生成的 JSX 元素存入缓存
    cache[0] = jsxElement;
  } else {
    // 如果缓存中已有值,直接复用缓存的 JSX 元素
    jsxElement = cache[0];
  }
  // 返回 JSX 元素,复用或重新生成的结果
  return jsxElement;
}
export default App;

14.5.2 支持状态 #

import { useState } from "react";
/* function Counter() {
    const [count, setCount] = useState(0);
    const handleClick = () => {
        setCount(count + 1);
    };
    return (
        <button onClick={handleClick}>{count}</button>
    );
} */
function Counter() {
  // 初始化缓存数组,支持最多 5 个缓存槽位
  const cache = _c(5);
  // 使用 useState 创建状态和更新函数
  const [count, setCount] = useState(0);
  // 缓存点击处理函数
  let incrementHandler;
  if (cache[0] !== count) {
    // 如果缓存中的计数值与当前计数不一致,更新缓存
    incrementHandler = () => setCount(count + 1);
    cache[0] = count; // 缓存当前计数值
    cache[1] = incrementHandler; // 缓存生成的点击处理函数
  } else {
    // 如果缓存一致,直接复用处理函数
    incrementHandler = cache[1];
  }
  // 缓存按钮 JSX 元素
  let buttonElement;
  if (cache[2] !== count || cache[3] !== incrementHandler) {
    // 如果计数值或处理函数发生变化,更新缓存
    buttonElement = <button onClick={incrementHandler}>{count}</button>;
    cache[2] = count; // 缓存当前计数值
    cache[3] = incrementHandler; // 缓存当前点击处理函数
    cache[4] = buttonElement; // 缓存生成的按钮元素
  } else {
    // 如果缓存一致,直接复用按钮元素
    buttonElement = cache[4];
  }
  // 返回按钮元素
  return buttonElement;
}
export default Counter;

14.5.3 支持属性 #

import { useState } from "react";
//import PropTypes from 'prop-types';
/* function Child({ count, onIncrement }) {
    return (
        <button onClick={onIncrement}>
            子组件中的计数: {count}
        </button>
    );
}
Child.propTypes = {
    count: PropTypes.number.isRequired,
    onIncrement: PropTypes.func.isRequired
};
function Counter() {
    const [count, setCount] = useState(0);
    const handleIncrement = () => {
        setCount(count + 1);
    };
    return (
        <div>
            <h2>父组件</h2>
            <Child
                count={count}
                onIncrement={handleIncrement}
            />
        </div>
    );
} */
function Child(props) {
  const cache = _c(3); // 缓存初始化,用于存储优化结果
  const { count, onIncrement } = props; // 解构 props
  let buttonElement;
  // 检查缓存是否需要更新
  if (cache[0] !== count || cache[1] !== onIncrement) {
    // 如果依赖变化,重新创建 JSX 元素
    buttonElement = (
      <button onClick={onIncrement}>子组件中的计数: {count}</button>
    );
    cache[0] = count; // 更新缓存的依赖
    cache[1] = onIncrement;
    cache[2] = buttonElement; // 更新缓存的 JSX 元素
  } else {
    // 如果依赖未变,复用缓存的 JSX 元素
    buttonElement = cache[2];
  }
  return buttonElement; // 返回按钮元素
}
function Counter() {
  const cache = _c(6); // 缓存初始化
  const [count, setCount] = useState(0); // 状态定义
  // 缓存 handleIncrement 函数
  let handleIncrement;
  if (cache[0] !== count) {
    handleIncrement = () => setCount(count + 1);
    cache[0] = count;
    cache[1] = handleIncrement;
  } else {
    handleIncrement = cache[1];
  }
  // 缓存标题元素
  let titleElement;
  if (cache[2] === Symbol.for("react.memo_cache_sentinel")) {
    titleElement = <h2>父组件</h2>;
    cache[2] = titleElement;
  } else {
    titleElement = cache[2];
  }
  // 缓存整个组件的 JSX
  let containerElement;
  if (cache[3] !== count || cache[4] !== handleIncrement) {
    containerElement = (
      <div>
        {titleElement}
        <Child count={count} onIncrement={handleIncrement} />
      </div>
    );
    cache[3] = count;
    cache[4] = handleIncrement;
    cache[5] = containerElement;
  } else {
    containerElement = cache[5];
  }
  return containerElement; // 返回最终的组件结构
}
export default Counter;

参考 #

1.useRef #

useRef 是 React 提供的一个 Hook,用于管理不参与组件渲染流程的可变引用(ref)。它非常轻量级且强大,适合在某些场景下替代 state,尤其是当数据的变化不需要触发组件重新渲染时。


基本语法

const refContainer = useRef(initialValue);

常见使用场景

  1. 访问 DOM 元素
    useRef 常用于直接访问 DOM 元素,替代 document.querySelectorgetElementById

  2. 保存不随渲染改变的变量
    用于存储跨渲染周期的可变值,而不触发重新渲染。

  3. 保存定时器或外部资源的引用
    在管理 setInterval 或某些外部 API 时,useRef 是一个很好的选择。

  4. 避免闭包问题
    在函数中避免访问陈旧的变量值。


详细示例

1. 访问 DOM 元素

通过 useRef 获取并操作某个 DOM 节点:

import React, { useRef } from "react";

function FocusInput() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus(); // 直接操作 DOM
  };

  return (
    <div>
      <input
        ref={inputRef}
        type="text"
        placeholder="Click the button to focus me"
      />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

export default FocusInput;

2. 保存不随渲染改变的变量

某些值需要在组件生命周期中保持不变,但又不适合放在 state 中,因为它们的变化不需要触发重新渲染。

import React, { useRef, useState } from "react";

function Counter() {
  const renderCount = useRef(0);
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);

  renderCount.current += 1;

  return (
    <div>
      <p>Count: {count}</p>
      <p>Rendered {renderCount.current} times</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default Counter;

2.useImperativeHandle #

useImperativeHandle 是 React 提供的一个高级 Hook,用于自定义通过 ref 暴露给父组件的实例值或方法。让父组件可以通过 ref 访问子组件中自定义的值或方法,而不仅仅是 DOM 节点。


基本语法

useImperativeHandle(ref, createHandle, [dependencies]);

使用场景

  1. 自定义暴露的方法

    • 当子组件需要提供一些特定的功能给父组件调用时,比如通过 ref 控制焦点、重置表单等。
  2. 隐藏内部实现细节

    • 父组件只能访问子组件暴露的方法或值,而不能直接访问子组件的内部状态。

详细示例

1. 控制 DOM 元素的行为

通过 useImperativeHandle 暴露方法,让父组件可以控制子组件的 DOM 行为。

import React, { useRef, useImperativeHandle } from "react";

const CustomInput = forwardRef(({ ref }) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = "";
    },
  }));

  return <input ref={inputRef} type="text" placeholder="Custom Input" />;
});

function App() {
  const inputRef = useRef();

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus Input</button>
      <button onClick={() => inputRef.current.clear()}>Clear Input</button>
    </div>
  );
}

export default App;

2. 自定义复杂组件行为

为复杂的子组件暴露特定方法,例如表单的验证和重置。

import React, { forwardRef, useImperativeHandle, useState } from "react";

const Form = forwardRef((props, ref) => {
  const [formData, setFormData] = useState({ name: "", email: "" });

  useImperativeHandle(ref, () => ({
    reset: () => {
      setFormData({ name: "", email: "" });
    },
    validate: () => {
      return formData.name !== "" && formData.email !== "";
    },
  }));

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  return (
    <div>
      <input
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
    </div>
  );
});

function App() {
  const formRef = useRef();

  const handleReset = () => {
    formRef.current.reset();
  };

  const handleValidate = () => {
    const isValid = formRef.current.validate();
    alert(isValid ? "Form is valid!" : "Form is invalid!");
  };

  return (
    <div>
      <Form ref={formRef} />
      <button onClick={handleReset}>Reset Form</button>
      <button onClick={handleValidate}>Validate Form</button>
    </div>
  );
}

export default App;

工作原理

  1. Ref

    • 子组件通过接收父组件传递的 ref,为 useImperativeHandle 提供基础。
  2. 暴露对象

    • useImperativeHandle 返回一个对象,定义子组件希望暴露的功能或值。
  3. 隔离内部实现

    • 父组件只能访问 useImperativeHandle 中定义的内容,无法直接修改子组件的内部状态或操作未暴露的逻辑。

注意事项

  1. 搭配 forwardRef 使用

    • useImperativeHandle 必须配合 forwardRef,否则父组件无法传递 ref
  2. 依赖数组的使用

    • 如果暴露的值或方法依赖于其他状态,记得将这些状态添加到 dependencies 数组中,确保更新后的值正确暴露。
  3. 谨慎使用

    • useImperativeHandle 是一种相对低级的 API,应在必要时才使用,例如控制外部无法直接访问的行为或细节。
    • 对于大部分场景,推荐使用 props 进行父子组件间的通信。

优点与限制

优点

限制


总结

useImperativeHandle 是 React 中一个强大的工具,用于自定义组件暴露给父组件的功能。 它适用于需要通过 ref 控制子组件行为的场景,比如自定义 DOM 操作、管理复杂表单、或暴露方法给父组件调用。

如果需要直接操控组件行为,并且这些行为不适合通过 props 管理,那么 useImperativeHandle 是一个非常适合的工具。

3.createContext #

在 React 中,createContext是创建上下文的核心 API,它允许开发者在组件树中传递数据,而无需通过每个层级显式传递 props。这个功能非常适合管理全局状态或共享数据,如主题、用户信息等。

**** 创建上下文

使用createContext可以创建一个上下文对象,该对象代表了组件可以读取或提供的上下文。创建上下文的基本语法如下:

import { createContext } from "react";

const MyContext = createContext(defaultValue);

**** 使用上下文

一旦创建了上下文对象,您可以使用它来包裹组件并提供上下文值。

<MyContext value={/* some value */}>
  {/* Your components */}
</MyContext>

在这个例子中,所有被包裹的组件都可以访问到提供的值。

**** 读取上下文

要在组件中读取上下文,可以使用useContext钩子,或者在类组件中使用contextType。以下是函数组件中使用useContext的示例:

import { useContext } from "react";

function MyComponent() {
  const value = useContext(MyContext);
  return <div>{value}</div>;
}

对于类组件,可以这样使用:

class MyComponent extends React.Component {
  static contextType = MyContext;

  render() {
    return <div>{this.context}</div>;
  }
}

React 19 的新特性

在 React 19 中,除了传统的使用方式外,引入了新的use()函数,使得在条件和循环中访问上下文变得更加灵活。例如:

import { use } from "react";

function FeatureComponent({ featureName }) {
  let featureEnabled = false;

  if (featureName) {
    featureEnabled = use(FeatureContext);
    return featureEnabled ? (
      <div>Feature Enabled!</div>
    ) : (
      <div>Feature Disabled.</div>
    );
  }
}

这个新特性允许开发者在需要时按需获取上下文值,从而优化性能,减少不必要的重新渲染。

总结

createContext是 React 中强大的工具,使得跨层级共享数据变得简单。在 React 19 中,通过引入新的 API 和钩子,开发者可以更灵活地管理和访问上下文,提高了代码的可读性和性能。通过合理使用这些功能,可以大大简化状态管理和数据共享的复杂性。

4.useDeferredValue #

useDeferredValue 是 React 18 引入的一个新 Hook,用于实现延迟更新。它可以帮助在 React 应用中优化性能,尤其是在处理高优先级和低优先级更新时提供更流畅的用户体验。


基本用法 useDeferredValue 接受一个值并返回一个延迟的值(deferred value)。如果传入的值在一段时间内发生了变化,useDeferredValue 会延迟更新返回的值,优先让其他更高优先级的任务完成。

语法:

const deferredValue = useDeferredValue(value);

适用场景

主要用于在用户输入或其他高优先级任务与复杂渲染之间平衡性能,例如:

  1. 搜索筛选
    • 用户快速输入内容时,优先更新输入框,而延迟更新复杂的筛选结果列表。
  2. 繁重的计算
    • 对输入值进行复杂计算的场景,通过延迟计算来避免卡顿。

核心特点

  1. 异步优先级调度
    • React 使用 Concurrent Mode(并发模式)会动态调整 useDeferredValue 的更新优先级。
  2. 非阻塞更新
    • 高优先级任务(如用户输入)不会被低优先级任务阻塞,确保界面流畅。

示例代码

输入搜索场景

import React, { useState, useDeferredValue } from "react";

function App() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);

  const searchResults = expensiveSearchFunction(deferredQuery);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <SearchResults results={searchResults} />
    </div>
  );
}

function expensiveSearchFunction(query) {
  // 模拟一个繁重的计算逻辑
  console.log("Computing search results for:", query);
  return Array(10000)
    .fill(0)
    .map((_, idx) => `Result ${idx} for "${query}"`);
}

function SearchResults({ results }) {
  return (
    <ul>
      {results.map((result, index) => (
        <li key={index}>{result}</li>
      ))}
    </ul>
  );
}

export default App;

运行流程:

  1. 用户快速输入时,query 会立即更新,用于确保输入框响应流畅。
  2. useDeferredValue 返回的 deferredQuery 可能会稍有延迟,确保繁重的搜索计算不会阻塞用户输入。

useTransition 的对比

示例:

const [isPending, startTransition] = useTransition();

function handleInputChange(e) {
  const value = e.target.value;
  startTransition(() => {
    setSearchQuery(value);
  });
}

useTransition 更适用于全局更新调度,而 useDeferredValue 是针对特定值的优化。


注意事项

  1. 性能提升的限制
    • 仅在并发模式下(Concurrent Mode)生效。
    • 复杂组件中效果更明显,简单组件中可能感觉不到提升。
  2. 依赖关系
    • 返回的 deferredValue 是一个状态值,类似于 useMemo,需要在依赖更新时重新计算。

总结:useDeferredValue 是一个强大的性能优化工具,用于高频更新场景,但需要结合 React 的并发模式才能最大化发挥其优势。在复杂的交互和计算任务中,它能显著提升用户体验。

5.useTransition #

useTransition 是 React 引入的一个新 Hook,用于控制更新优先级,允许将某些更新标记为“过渡任务”(Transition)。通过将任务分为高优先级和低优先级,可以提升用户体验,尤其是在处理复杂、耗时的更新时。


基本用法 useTransition 的核心在于异步调度,确保高优先级任务(如用户输入)始终快速响应,而将低优先级任务(如复杂渲染)延迟执行。

语法:

const [isPending, startTransition] = useTransition();

核心概念

  1. 高优先级 vs 低优先级任务

    • 高优先级任务:直接影响用户体验的任务,比如输入框的即时更新。
    • 低优先级任务:不需要立即完成的任务,比如搜索结果的渲染。
  2. 挂起状态

    • 当过渡任务正在执行时,isPendingtrue,可以用它来显示加载指示或其他 UI 提示。

示例代码

输入搜索场景

import React, { useState, useTransition } from "react";

function App() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 高优先级任务:立即更新输入框

    startTransition(() => {
      const searchResults = expensiveSearchFunction(value);
      setResults(searchResults); // 低优先级任务:延迟更新结果
    });
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
        placeholder="Search..."
      />
      {isPending && <p>Loading...</p>}
      <SearchResults results={results} />
    </div>
  );
}

function expensiveSearchFunction(query) {
  // 模拟一个繁重的计算逻辑
  console.log("Computing search results for:", query);
  return Array(10000)
    .fill(0)
    .map((_, idx) => `Result ${idx} for "${query}"`);
}

function SearchResults({ results }) {
  return (
    <ul>
      {results.map((result, index) => (
        <li key={index}>{result}</li>
      ))}
    </ul>
  );
}

export default App;

运行流程:

  1. 用户快速输入时,输入框 query 会立即更新(高优先级任务)。
  2. 搜索结果的计算和渲染(低优先级任务)通过 startTransition 延迟,防止卡顿。
  3. 在结果挂起期间,isPendingtrue,显示“Loading...”提示。

useTransition 的优点

  1. 提升交互流畅性
    • 优先完成用户交互任务,降低繁重任务的阻塞感。
  2. 更高的可控性
    • 可精确控制哪些更新需要延迟。
  3. 减少性能瓶颈
    • 对复杂任务分层处理,避免性能密集型任务影响实时交互。

useDeferredValue 的对比

对比场景:


注意事项

  1. 仅在并发模式下生效
    • useTransition 的延迟机制依赖 React 的 Concurrent Mode。
  2. 适用于复杂更新
    • 对简单更新场景使用可能不会有明显效果。

总结 useTransition 是 React 提供的性能优化工具,适用于需要将更新分为高低优先级的复杂场景。它通过标记低优先级任务为“过渡任务”,为用户提供更加流畅的交互体验,同时确保繁重的计算或渲染不会阻塞关键任务。

6.formData #

FormData 是 HTML5 提供的一个内置接口,常用于以编程方式构建和管理表单数据,尤其是在与服务器交互(如通过 fetchXMLHttpRequest 提交表单)时。

它允许开发者轻松构建键值对形式的表单数据,同时支持文件上传等复杂操作。


创建 FormData 对象

  1. 从已有表单元素构建

    • 直接使用 <form> 元素创建 FormData,自动收集表单中所有具有 name 属性的字段数据。
    const form = document.querySelector("form");
    const formData = new FormData(form);
    
  2. 空表单数据对象

    • 手动创建空的 FormData 对象,并动态添加数据。
      const formData = new FormData();
      formData.append("key", "value");
      

常用方法

1. appendFormData 对象添加一个键值对。

formData.append(name, value, filename);
formData.append("username", "JohnDoe");
formData.append("profile", fileInput.files[0], "profile.png"); // 上传文件

2. get 获取指定键的值(如果有多个值,则只返回第一个)。

const value = formData.get(name);
console.log(value); // "JohnDoe"

3. getAll 获取指定键的所有值(如果键有多个值)。

const values = formData.getAll(name);
console.log(values); // ["value1", "value2"]

4. set 设置指定键的值(如果键已存在,则覆盖)。

formData.set("username", "JaneDoe");

5. delete 删除指定键及其所有值。

formData.delete("username");

6. has 检查指定键是否存在。

console.log(formData.has("username")); // true 或 false

7. entries 返回一个迭代器,遍历所有键值对。

for (const [key, value] of formData.entries()) {
  console.log(key, value);
}

8. keys 返回一个迭代器,遍历所有键。

for (const key of formData.keys()) {
  console.log(key);
}

9. values 返回一个迭代器,遍历所有值。

for (const value of formData.values()) {
  console.log(value);
}

fetch 结合提交表单

使用 FormDatafetch 一起提交表单数据。

const form = document.querySelector("form");
const formData = new FormData(form);

fetch("https://example.com/submit", {
  method: "POST",
  body: formData,
})
  .then((response) => response.json())
  .then((data) => {
    console.log("Success:", data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

优点


示例:动态构建和提交 FormData

const formData = new FormData();

// 动态添加字段
formData.append("username", "JohnDoe");
formData.append("age", 30);

// 添加文件
const fileInput = document.querySelector("#file");
formData.append("profile", fileInput.files[0]);

// 提交数据
fetch("https://example.com/submit", {
  method: "POST",
  body: formData,
}).then((response) => {
  console.log("Form submitted successfully!");
});

文件上传场景

利用 FormData 上传文件时,文件字段的值为 FileBlob 对象。

HTML 示例

<form id="uploadForm">
  <input type="file" name="photo" />
  <button type="submit">Upload</button>
</form>

JavaScript 示例

const form = document.getElementById("uploadForm");

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const formData = new FormData(form);

  fetch("https://example.com/upload", {
    method: "POST",
    body: formData,
  })
    .then((response) => response.json())
    .then((data) => {
      console.log("Upload successful:", data);
    })
    .catch((error) => {
      console.error("Upload error:", error);
    });
});

注意事项

  1. 文件上传支持

    • FormData 支持文件上传,将文件通过 <input type="file"> 添加到表单中即可。
    • 上传文件时无需手动设置 Content-Typefetch 会自动处理。
  2. 跨域问题

    • 提交表单到跨域的服务器时,需确保后端支持 CORS(跨域资源共享)。
  3. 调试 FormData

    • FormData 无法直接通过 console.log 查看内容,需使用迭代方法(如 entries())。
  4. 序列化为字符串

    • 如果需要将 FormData 序列化为 URL 编码的字符串(如 application/x-www-form-urlencoded),可以使用以下方法:
      const params = new URLSearchParams(formData);
      console.log(params.toString());
      

总结

7.useActionState #

useActionState 是 React 19 引入的一个新的 Hook,主要用于处理表单提交状态和结果。

  1. 基本用法
import { useActionState } from "react";

function MyForm() {
  const [formState, formAction] = useActionState(async (formData) => {
    // 处理表单提交
    const result = await submitData(formData);
    return result;
  });

  return (
    <form action={formAction}>
      {formState.pending && <p>提交中...</p>}
      {formState.error && <p>错误:{formState.error.message}</p>}
      {formState.data && <p>提交成功!</p>}
      <button type="submit">提交</button>
    </form>
  );
}
  1. 状态属性

useActionState 返回的状态对象包含以下属性:

{
  pending: boolean,    // 是否正在处理
  data: any,          // 成功时的返回数据
  error: Error,       // 错误信息
  status: string      // 当前状态:'idle' | 'pending' | 'success' | 'error'
}
  1. 实际应用示例
function RegistrationForm() {
  const [state, formAction] = useActionState(async (formData) => {
    // 模拟 API 调用
    if (formData.get("email").includes("test")) {
      throw new Error("测试邮箱不允许注册");
    }

    return { message: "注册成功" };
  });

  return (
    <form action={formAction}>
      <div>
        <input name="email" type="email" required />
        <input name="password" type="password" required />
      </div>

      {/* 状态展示 */}
      {state.pending && <div>注册中...</div>}
      {state.error && <div className="error">{state.error.message}</div>}
      {state.data && <div className="success">{state.data.message}</div>}

      <button type="submit" disabled={state.pending}>
        {state.pending ? "处理中..." : "注册"}
      </button>
    </form>
  );
}
  1. 主要优势

  2. 状态管理自动化

    • 自动处理加载状态
    • 自动处理错误状态
    • 自动处理成功状态
  3. 更简洁的代码

    • 不需要手动管理多个状态变量
    • 不需要编写复杂的 try-catch 逻辑
  4. 更好的用户体验

    • 可以轻松实现加载指示器
    • 可以优雅处理错误状态
    • 可以展示成功反馈
  5. 进阶用法

function ComplexForm() {
  const [state, formAction] = useActionState(
    async (formData, { signal }) => {
      // 支持中断请求
      const response = await fetch("/api/submit", {
        method: "POST",
        body: formData,
        signal,
      });

      if (!response.ok) {
        throw new Error("提交失败");
      }

      return await response.json();
    },
    {
      // 可选配置项
      initialState: { data: null },
      optimisticUpdate: (formData) => ({
        // 乐观更新
        message: "正在处理...",
      }),
    }
  );

  return <form action={formAction}>{/* 表单内容 */}</form>;
}
  1. 注意事项

  2. useActionState 必须在组件顶层调用

  3. 回调函数应该是幂等的(多次调用结果相同)
  4. 建议处理所有可能的错误情况
  5. 可以配合 useTransition 使用来优化用户体验

这个新的 Hook 大大简化了表单处理和状态管理的复杂度,使得开发者可以更专注于业务逻辑的实现,而不是重复编写状态管理的模板代码。

8. useOptimistic #

useOptimistic 是 React 19 中新增的一个 Hook,专门设计用于处理乐观更新的场景。乐观更新是一种优化技术,用于在用户与界面交互时,立即更新 UI,从而提供更流畅的体验,而无需等待后端的响应。useOptimistic 使得这一过程更加简单和直观。


基本用法

useOptimistic 提供了一种声明式的方法来管理乐观状态。它的基本形式如下:

const [optimisticState, addOptimisticUpdate] = useOptimistic(
  initialState,
  reducer
);

参数详解

  1. initialState

    • 你希望乐观更新的初始数据。
    • 例如,当前的列表、计数值或其他可以显示在 UI 上的数据。
  2. reducer

    • 定义如何根据用户的交互或特定事件更新状态。
    • 这个函数接收两个参数:
      • 当前状态(state
      • 操作描述符(action
    • 返回更新后的状态。

返回值

useOptimistic 返回一个数组,包含两个元素:

  1. optimisticState

    • 当前的乐观状态值。
    • 在 UI 中可以直接绑定使用。
  2. addOptimisticUpdate

    • 一个函数,用于触发新的乐观更新。
    • 调用这个函数时,会执行 reducer,并立即更新 optimisticState

示例

以下是一个简单的乐观更新示例:用户点击按钮时立即更新计数,而无需等待服务器的响应。

import React from "react";
import { useOptimistic } from "react";

function Counter() {
  const [count, addOptimisticUpdate] = useOptimistic(0, (state, action) => {
    switch (action.type) {
      case "increment":
        return state + 1;
      case "decrement":
        return state - 1;
      default:
        return state;
    }
  });

  const handleIncrement = async () => {
    // 乐观更新
    addOptimisticUpdate({ type: "increment" });

    try {
      // 模拟向服务器发送请求
      await fetch("/api/increment", { method: "POST" });
    } catch (error) {
      console.error("服务器请求失败,可能需要回滚");
      // 根据需要回滚
    }
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>+1</button>
    </div>
  );
}

核心特点

  1. 简单声明式 API

    • 不需要手动管理多个状态变量。
    • 状态变化逻辑通过 reducer 集中管理,易于维护。
  2. 自动与异步操作结合

    • 乐观状态的更新可以立即反映在 UI 上,同时允许你处理后续的异步请求。
  3. 灵活的状态管理

    • 支持复杂的状态更新逻辑,不局限于简单的加减操作。

常见用例

  1. 表单提交

    • 在用户提交表单时立即更新 UI,例如添加新评论或任务。
  2. 列表操作

    • 例如,删除列表中的某一项时,立即从 UI 中移除,而不等待服务器确认。
  3. 计数操作

    • 用户点击按钮时立即更新计数器,而不是等待后端完成更新。

注意事项

  1. 回滚逻辑

    • 如果服务器请求失败,需要明确如何回滚乐观状态。这通常需要重新加载状态或引导用户手动修正。
  2. 数据一致性

    • 乐观更新在性能上有优势,但可能引发数据不一致问题。如果后端的最终状态与乐观状态冲突,需要明确如何同步。
  3. 不要滥用

    • 在不必要的场景下滥用乐观更新可能引发用户混淆,特别是在后端响应速度较快的情况下。

总结

useOptimistic 是一个非常强大的工具,能显著简化乐观更新的实现,同时提升用户体验。通过声明式 API 和 reducer 管理状态变化,你可以快速构建高效、响应迅速的交互式应用。

9. react-compiler-healthcheck #

React Compiler Healthcheck

React Compiler Healthcheck 是一个工具,用于帮助开发者分析代码库是否符合 React Compiler(如 React Forget 编译器)的优化要求,以及检测可能影响自动化优化的代码问题。它的目标是提升代码质量并确保编译器可以正确处理代码,最大化 React 应用的性能潜力。


React Compiler Healthcheck 的核心功能

  1. 自动分析代码库

    • 扫描代码库以识别不符合 React Compiler 最佳实践的代码。
    • 提供具体的代码片段和位置,帮助开发者快速定位问题。
  2. 识别常见问题

    • 找出阻碍编译器优化的代码问题,例如:
      • 未正确处理的依赖项。
      • 不必要的重新渲染。
      • 难以推断的状态更新逻辑。
    • 提供详细的建议或修复步骤。
  3. 生成健康报告

    • 输出一份健康报告,显示代码库中符合优化要求的程度。
    • 提供分数或评级,让团队可以衡量代码优化的进展。
  4. 与 ESLint 集成

    • 提供 ESLint 插件,将健康检查结果集成到开发者的编辑器中,实时提示潜在问题。

安装与使用

安装 React Compiler Healthcheck 可能是独立的工具或集成在 React DevTools、ESLint 插件中,安装方式如下:

npm install -D react-compiler-healthcheck@beta eslint-plugin-react-compiler@beta

或者使用 Yarn:

yarn add -D react-compiler-healthcheck@beta eslint-plugin-react-compiler@beta

运行检查 使用命令行工具运行健康检查:

npx react-compiler-healthcheck

此命令会扫描整个代码库,并输出一份详细的检查报告。

配置 ESLint 通过添加 ESLint 插件,可以在编辑器中实时查看健康检查的反馈:

  1. .eslintrc.js 文件中配置:

    module.exports = {
      plugins: ["react-compiler"],
      rules: {
        "react-compiler/no-memo-issues": "warn", // 示例规则
      },
    };
    
  2. 编辑器中会显示潜在问题的警告或错误信息。


健康检查的关键检查点

  1. 依赖项问题

    • useEffectuseMemouseCallback 中依赖项错误可能导致性能问题或不必要的重新渲染。
    • 检查点:
      • 未声明的依赖项。
      • 多余或错误的依赖项。
  2. 复杂状态管理

    • React Compiler 在处理复杂的状态管理时可能面临优化困难。
    • 检查点:
      • 使用了嵌套过深的对象或数组状态。
      • 状态更新逻辑难以推断。
  3. 组件重渲染

    • 组件重渲染过于频繁可能导致性能瓶颈。
    • 检查点:
      • 缺少 React.memo 包裹的组件。
      • 不必要的父组件状态更新导致子组件重新渲染。
  4. 代码可预测性

    • 编译器需要代码保持简洁且逻辑清晰。
    • 检查点:
      • 使用了动态复杂逻辑,导致编译器无法静态分析。
      • 难以追踪的变量依赖关系。

输出示例

当运行健康检查工具后,会生成如下报告:

React Compiler Healthcheck Report
=================================
✔  Scan completed: 100 files analyzed.

Issues found:
1. useEffect dependency array incomplete. (src/components/MyComponent.js:15)
   Suggestion: Add [props.data] to the dependency array.

2. React.memo missing for heavy component. (src/components/HeavyList.js:32)
   Suggestion: Wrap component with React.memo to prevent unnecessary re-renders.

3. Unnecessary state updates in nested loops. (src/utils/calculate.js:10)
   Suggestion: Simplify state management logic.

Overall Health Score: 75/100
Recommendation: Address high-priority issues to unlock React Compiler optimizations.

工具的优势

  1. 提升代码质量

    • 自动发现阻碍优化的问题,提高代码的可维护性和性能。
  2. 开发者友好

    • 提供详细的修复建议和文档参考,让开发者快速理解问题。
  3. 优化 React 应用性能

    • 确保 React Compiler 能够充分发挥作用,减少不必要的渲染和性能开销。

注意事项

  1. Beta 阶段

    • React Compiler Healthcheck 当前可能仍处于 Beta 测试阶段,某些功能可能不稳定。
  2. 团队协作

    • 健康检查的报告和建议应与团队共享,作为代码质量改进的依据。
  3. 与现有工具结合

    • 推荐与 ESLint、Prettier 等代码检查工具集成,共同维护最佳代码实践。

总结

React Compiler Healthcheck 是一款专为优化 React 代码库设计的工具。通过分析代码中可能影响 React Compiler 自动优化的潜在问题,并提供修复建议,它可以帮助开发者构建更高效、更易维护的 React 应用。随着 React Compiler 的逐步普及,这一工具将成为开发者日常工作的重要组成部分。

10. React Compiler- #

React Compiler 是 React 官方推出的新一代工具,旨在编译和优化 React 代码,以提高应用的性能和开发效率。它通过分析和转换代码,在编译阶段引入自动优化机制,使得开发者无需手动调整代码来实现性能优化。

React Compiler 的目标是减少运行时开销提升渲染效率,并为开发者提供更好的代码开发体验。


React Compiler 的核心功能

  1. 自动记忆化(Automatic Memoization)

  2. 原理:编译器可以自动检测组件、状态和钩子是否需要记忆化,从而避免不必要的重新计算和重新渲染。

  3. 好处
    • 开发者无需手动使用 React.memouseMemouseCallback
    • 避免了记忆化滥用或遗漏带来的性能问题。

示例:

原始代码:

function MyComponent({ count }) {
  return <div>{count}</div>;
}

编译后:

function MyComponent({ count }) {
  const cache = _c(1); // 缓存初始化
  let jsxElement;

  if (cache[0] !== count) {
    jsxElement = <div>{count}</div>;
    cache[0] = count; // 更新缓存
    cache[1] = jsxElement;
  } else {
    jsxElement = cache[1]; // 复用缓存
  }

  return jsxElement;
}

编译器自动将组件的输出缓存起来,避免了重复生成 JSX。


  1. 依赖项分析

  2. 原理:编译器能静态分析钩子(如 useEffectuseMemo)的依赖项,确保依赖声明的完整性。

  3. 好处
    • 自动补全遗漏的依赖项,减少开发错误。
    • 避免因依赖不完整导致的逻辑错误或性能问题。

示例:

原始代码:

useEffect(() => {
  console.log("Count updated:", count);
}, []);

编译后:

useEffect(() => {
  console.log("Count updated:", count);
}, [count]); // 自动添加依赖项

  1. 静态优化

  2. 原理:识别代码中的静态部分,在编译阶段将其优化为常量或缓存结构。

  3. 好处
    • 减少运行时不必要的计算。
    • 提高组件的首次渲染速度。

示例:

原始代码:

function App() {
  return <div>Hello, World!</div>;
}

编译后:

const StaticContent = <div>Hello, World!</div>;

function App() {
  return StaticContent; // 静态内容直接复用
}

  1. 智能状态管理

  2. 原理:对组件状态的使用进行静态分析,优化状态更新逻辑和依赖关系。

  3. 好处
    • 避免不必要的状态更新,提升性能。
    • 自动生成高效的状态变更代码。

React Compiler 的特点

  1. 开发者无感知

    • React Compiler 在编译阶段完成优化,开发者无需手动改动代码。
    • 与 Babel 和 Webpack 等工具无缝集成。
  2. 兼容现有代码

    • 编译器不会破坏已有代码逻辑或引入新的语法,完全向后兼容。
  3. 与运行时配合

    • React Compiler 和 React 运行时(如 React.memouseMemo 等)协同工作,进一步提高应用性能。
  4. 易于调试

    • 提供调试工具,如 ESLint 插件(eslint-plugin-react-compiler),帮助开发者在编辑器中检测和优化代码。

React Compiler 的使用方法

  1. 安装相关工具 React Compiler 可能通过 Babel 插件提供:
npm install -D babel-plugin-react-compiler
  1. 配置 Babel.babelrc 文件中添加:
{
  "plugins": ["react-compiler"]
}
  1. 运行项目 正常运行项目时,React Compiler 会自动优化代码。

React Compiler 的优劣势

优点

  1. 性能优化
    • 编译器自动优化组件渲染、状态管理和依赖项分析,提升性能。
  2. 简化开发
    • 开发者不再需要手动添加 React.memouseMemo,减少优化代码的复杂度。
  3. 防止错误
    • 自动补全依赖项,减少常见的依赖遗漏问题。

缺点

  1. 学习曲线
    • 对于想深入了解其工作机制的开发者,需要理解缓存机制和编译优化的原理。
  2. 限制场景
    • 在某些动态或极度复杂的逻辑下,编译器可能无法有效优化。

未来展望

React Compiler 是 React 性能优化生态的重要组成部分,未来可能进一步扩展功能,例如:


总结

React Compiler 是一项革命性工具,通过在编译阶段对 React 代码进行深度优化,帮助开发者简化性能优化工作,提高应用的效率和用户体验。它让开发者专注于业务逻辑,而不是陷入复杂的优化代码中,是未来 React 应用开发的重要方向。