0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 11 关 含代码 约 10 分钟

保持组件纯粹:纯函数原则与副作用隔离

保持组件纯粹:纯函数原则与副作用隔离

在数学中,公式 y=2xy = 2x 具有一种非常优美的特质:无论你在昨天计算、今天计算、在地球计算还是在月球计算,只要 x=3x = 3,yy 的答案永远且唯一地等于 66。它绝对不会因为今天天气不好,就突然算出 y=7y = 7。

这种只负责根据输入计算输出、绝不产生节外生枝副作用的函数,在编程中被称为 纯函数(Pure Function)。

React 的整个架构都是建立在这个概念之上的。React 假定你编写的每一个组件都是纯函数!


1. 什么是纯函数?

一个函数只要满足以下两点,它就是纯函数:

  1. 只管自己的业务(Minds its own business):在被调用之前就已存在的任何对象或变量,它都不会去修改;
  2. 相同的输入,相同的输出(Same inputs, same outputs):只要传入的参数相同,无论被调用多少次,返回的 JSX 必须一模一样。

2. 反例演示:违反纯函数原则的杯子计数器

看看下面这个组件,它试图计算来访的客人杯数:

// ❌ 极度危险的不纯组件!
let guest = 0; // 在组件外部定义了一个全局变量

function Cup() {
  // 缺陷:在渲染计算过程中直接修改了外部预先存在的变量!
  guest = guest + 1;
  return <h2>为第 {guest} 位客人准备的茶杯</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

发生了什么问题?

这个组件在渲染自己的时候,偷偷修改了外部变量 guest。 如果你渲染三次 <Cup />,它可能显示 1、2、3。但如果页面其他地方更新,导致 TeaSet 重新渲染,guest 就会从 3 变成 4、5、6!每次重新渲染,界面上的数字就全乱了套。

纯洁的改写方案:通过 Props 传参

让组件符合数学公式的纯洁性:需要什么数据,就从参数里光明正大地传进来!

// ✅ 纯函数组件
function Cup({ guest }) {
  return <h2>为第 {guest} 位客人准备的茶杯</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

现在,只要传入 guest={1},它就永远返回“为第 1 位客人准备的茶杯”,没有任何意外。


3. 为什么 React 在开发模式下会渲染两次?(StrictMode 的秘密)

在学习 React 时,你可能会发现一个奇怪的现象:如果你在组件函数里写一个 console.log('渲染了'),控制台总是会连续打印两次!

这是因为 React 在开发环境下默认开启了 严格模式(StrictMode)。

为什么要执行两次?

在生产环境中,React 只会渲染一次。但在开发环境中,React 故意把你的每个组件函数连续调用两次! 其目的非常简单直接:帮你揪出像上面那种“不纯”的组件!

  • 如果你的组件是纯函数,调用两次得到的结果完全一致,界面丝毫不受影响;
  • 如果你的组件不纯(比如修改了外部变量),调用两次就会立即露馅(比如杯子数字突然跳成了 2、4、6),让你在上线前立即发现并修复隐藏的致命 bug。

4. 什么是合法的“局部突变”(Local Mutation)?

纯函数规则要求:不能修改在调用前就已存在的变量。 但是,在组件函数内部刚刚新建的变量,是完全可以随意修改的! 这被称为 局部突变(Local Mutation)。

看下面这个例子:

function Cup({ guest }) {
  return <h2>为第 {guest} 位客人准备的茶杯</h2>;
}

export default function TeaGathering() {
  const cups = []; // 在函数内部新建的局部数组
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />); // 尽管调用了 push,但这是完全纯粹的!
  }
  return cups;
}

因为 cups 是在本次 TeaGathering 执行过程中才从无到有创建的,外界任何其他组件都看不到它。在函数算完并返回之前,你可以在内部用 for 循环或 push 填充它。这完全符合纯函数的定义!


5. 那么,副作用(Side Effects)到底应该发生在哪里?

页面开发总归是需要改变东西的:向服务器发送请求、弹出一个提示框、跳转路由、保存数据。这些改变外部系统的行为都属于 副作用(Side Effects)。

React 并不反对副作用,但要求:副作用不能发生在组件的渲染计算过程中!

在 React 中,副作用主要居住在两个专门的庇护所中:

  1. 事件处理函数(Event Handlers):比如用户点击按钮时触发的 handleClick 函数。它们在组件渲染完成后等待用户操作才执行,不属于纯计算渲染代码;
  2. useEffect Hook:当某些副作用确实必须由页面加载自动引发(例如拉取远程数据),React 提供了专门的 useEffect 机制,在渲染完成提交到 DOM 之后再去执行(我们会在本课程的最后一节深入学习)。

总结

  • React 组件必须是纯函数:给定相同输入,必须返回相同 JSX;
  • 绝不在渲染计算中修改外部已经存在的变量或对象;
  • 在组件内部创建并修改的临时变量是合法的局部突变;
  • StrictMode 在开发模式下调用组件两次,目的是帮你尽早发现不纯组件的隐蔽 bug;
  • 更改系统和提交请求等副作用,应当放在事件处理程序或 Effect 中。
下一站:将 UI 视为树:渲染树与模块依赖树 →
© 2026 React 官方教程