深入理解React副作用

25 阅读3分钟

在React中,副作用就是组件在渲染之外做的“额外的事情”,这些事情会影响到组件外部的世界。想象你在做饭(渲染组件),主要任务是把菜做好(生成界面)。但如果你在炒菜的同时,还顺手打开了电视、接了个电话、或者去敲邻居的门,这些“额外的事情”就是副作用,因为它们超出了“做菜”这个核心任务。

在React里,组件的主要任务是根据数据(状态或props)生成界面(JSX)。但有时候,组件还需要做一些“杂事”,比如:

  • 从网上抓数据(比如请求API)。
  • 设置一个定时器(比如每秒更新时间)。
  • 直接改网页上的某个元素(比如改标题)。
  • 监听用户的操作(比如监听窗口大小变化)。

这些“杂事”就是副作用,因为它们不是直接生成界面,而是影响了外部的东西(网络、浏览器、DOM等)

为什么React要特别管理副作用?

React希望组件的渲染(生成界面)是“纯净”的,就像一道数学公式:给相同的输入(状态/props),总是得到相同的输出(界面)。但副作用会让事情变得“杂乱”,因为它们可能会改变外部的东西,或者依赖外部的不可控因素(比如网络请求的结果)。所以,React用useEffect把这些“杂事”隔离起来,单独处理。

举个生活中的例子

假设你在画一幅画(类似React组件渲染):

  • 主要任务:根据你的想法(状态),画出一幅漂亮的画(界面)。
  • 副作用:你在画画的过程中,还顺便:
    • 打电话问朋友意见(类似API请求)。
    • 设置一个闹钟提醒自己休息(类似定时器)。
    • 在画布旁边写字(类似操作DOM)。

这些“顺便”做的事就是副作用,它们跟画画本身无关,但可能会影响外界(比如朋友、闹钟、画布外的区域)。

用React代码简单看一下

假设你写了个React组件,显示一个计数器:

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

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>加1</button>
    </div>
  );
}

这里,组件的主要任务是显示count和一个按钮,渲染出界面。这是“画画”的部分。

现在,如果你想在组件加载时从服务器获取初始的count值,或者每秒自动加1,这些就是副作用。因为它们:

  • 涉及外部系统(服务器、定时器)。
  • 不是直接生成界面的工作。

用useEffect处理这些副作用:

import { useState, useEffect } from 'react';

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

  // 副作用:从服务器获取数据
  useEffect(() => {
    fetch('https://api.example.com/count')
      .then(res => res.json())
      .then(data => setCount(data.count));
  }, []); // 空数组,只在组件第一次加载时运行

  // 副作用:设置定时器每秒加1
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    return () => clearInterval(timer); // 清理定时器,防止内存泄漏
  }, []);

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>加1</button>
    </div>
  );
}

重点总结

  • 副作用是“额外的事情”:组件渲染(生成界面)之外的、影响外部的操作,比如网络请求、定时器、DOM操作。
  • 为什么隔离副作用:React希望渲染过程简单、可预测,副作用会让事情复杂,所以用useEffect单独管理。
  • 怎么理解useEffect:它就像一个“杂事管家”,在组件渲染完后处理那些“额外的事情”,还能在需要时清理(比如取消定时器)。