<

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. 更简单的代码:

    • 不再需要 useState 和 useEffect 手动管理异步状态。
    • 避免了常见的异步问题,如竞态条件或错误处理的疏漏。
  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 提供的一个钩子,用于在表单提交后处理状态。它返回一个对象,其中包含 data 和 error 属性,分别表示表单提交后的数据和错误信息。

// 导入 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.querySelector 或 getElementById。

  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. 挂起状态

    • 当过渡任务正在执行时,isPending 为 true,可以用它来显示加载指示或其他 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. 在结果挂起期间,isPending 为 true,显示“Loading...”提示。

useTransition 的优点

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

与 useDeferredValue 的对比

对比场景:


注意事项

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

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

6.formData #

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

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


创建 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. append 向 FormData 对象添加一个键值对。

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 结合提交表单

使用 FormData 和 fetch 一起提交表单数据。

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 上传文件时,文件字段的值为 File 或 Blob 对象。

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-Type,fetch 会自动处理。
  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. 依赖项问题

    • useEffect、useMemo 或 useCallback 中依赖项错误可能导致性能问题或不必要的重新渲染。
    • 检查点:
      • 未声明的依赖项。
      • 多余或错误的依赖项。
  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.memo、useMemo 或 useCallback。
    • 避免了记忆化滥用或遗漏带来的性能问题。

示例:

原始代码:

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. 原理:编译器能静态分析钩子(如 useEffect 和 useMemo)的依赖项,确保依赖声明的完整性。

  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.memo、useMemo 等)协同工作,进一步提高应用性能。
  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.memo 或 useMemo,减少优化代码的复杂度。
  3. 防止错误
    • 自动补全依赖项,减少常见的依赖遗漏问题。

缺点

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

未来展望

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


总结

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