React 中的 useEffect 详解

kkcode
kkcode
2026-02-13阅读 810

useEffect 是 React 中另一个核心 Hook,主要用于处理副作用(Side Effects),可以把它理解为函数组件中替代类组件生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount)的统一方案。

一、核心作用

  • 处理组件的副作用操作:比如数据请求、DOM 操作、订阅事件、定时器、清理定时器/订阅等。
  • 监听状态/属性变化,触发对应逻辑:当指定依赖项改变时,自动执行副作用函数。
  • 实现组件的生命周期逻辑:统一管理组件挂载、更新、卸载时的操作,避免分散在多个生命周期方法中。

二、基本用法

1. 引入与语法

react 中导入 useEffect,在函数组件顶层调用,语法如下:

import { useEffect } from 'react';

function 组件名() {
  // 语法:useEffect(副作用函数, 依赖数组);
  useEffect(() => {
    // 执行副作用操作(如请求数据、操作DOM)
    
    // 可选:返回清理函数,组件卸载或依赖变化时执行
    return () => {
      // 清理操作(如清除定时器、取消订阅)
    };
  }, [依赖项1, 依赖项2]); // 依赖数组:指定触发副作用的变量
  
  return UI结构;
}

2. 关键参数说明

  • 副作用函数:要执行的逻辑代码,比如发送网络请求、修改 DOM、设置定时器等。
  • 依赖数组(可选):控制副作用函数的执行时机,是 useEffect 的核心:
    1. 不传依赖数组:组件**每次渲染(挂载+更新)**后都会执行副作用函数。
    2. 空数组 []:仅在组件首次挂载时执行一次,类似 componentDidMount
    3. 包含依赖项:仅当依赖项中的变量发生变化时,才会执行副作用函数,类似 componentDidUpdate
  • 清理函数(可选):副作用函数返回的函数,用于清理副作用(如清除定时器、取消网络请求),在组件卸载前下一次副作用执行前触发,避免内存泄漏。

三、实用示例

示例 1:基础数据请求(挂载时执行一次)

最常见的场景是组件挂载后请求后端数据,空依赖数组确保只执行一次:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  // 空依赖数组:仅组件挂载时执行一次
  useEffect(() => {
    // 异步请求数据
    const fetchUsers = async () => {
      try {
        const res = await fetch('https://api.example.com/users');
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        console.error('请求失败:', err);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // 空依赖,只执行一次

  if (loading) return <div>加载中...</div>;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

示例 2:监听状态变化(带依赖项)

当指定状态变化时,自动触发副作用逻辑,比如根据搜索关键词请求数据:

import { useState, useEffect } from 'react';

function Search() {
  const [keyword, setKeyword] = useState('');
  const [result, setResult] = useState([]);

  // 依赖项包含 keyword:仅 keyword 变化时执行
  useEffect(() => {
    // 模拟根据关键词搜索
    const searchData = async () => {
      if (!keyword) {
        setResult([]);
        return;
      }
      // 模拟请求延迟
      const timer = setTimeout(() => {
        setResult([`结果1:${keyword}`, `结果2:${keyword}`]);
      }, 500);

      // 清理:关键词变化时清除上一次定时器,避免重复请求
      return () => clearTimeout(timer);
    };

    searchData();
  }, [keyword]); // 依赖 keyword,变化则执行

  return (
    <div>
      <input
        type="text"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="输入搜索关键词"
      />
      <ul>
        {result.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

示例 3:定时器与清理(避免内存泄漏)

组件挂载时设置定时器,卸载时必须清理,否则会造成内存泄漏:

import { useState, useEffect } from 'react';

function Timer() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    // 挂载时设置定时器
    const timer = setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);

    // 清理函数:组件卸载时清除定时器
    return () => clearInterval(timer);
  }, []); // 空依赖,只设置一次定时器

  return <div>当前计时:{time} 秒</div>;
}

示例 4:DOM 操作(修改页面标题)

直接操作 DOM 属于副作用,通过 useEffect 实现,监听状态变化同步更新标题:

import { useState, useEffect } from 'react';

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

  // 每次 count 变化,更新页面标题
  useEffect(() => {
    document.title = `计数:${count}`;
  }, [count]); // 依赖 count,变化则更新标题

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

四、核心特性

  1. 统一生命周期:一个 Hook 替代 componentDidMountcomponentDidUpdatecomponentWillUnmount,逻辑更集中。
  2. 依赖驱动执行:通过依赖数组精准控制副作用的触发时机,避免不必要的重复执行。
  3. 自动清理机制:返回清理函数,无需手动管理卸载逻辑,有效防止内存泄漏。
  4. 可多次使用:一个组件中可以使用多个 useEffect,分离不同的副作用逻辑(如一个处理请求,一个处理定时器)。
  5. 执行时机:默认在浏览器绘制完成后异步执行,不会阻塞页面渲染;若需同步执行(如测量 DOM),可使用 useLayoutEffect

五、常见误区

  1. 遗漏依赖项:副作用函数中使用了外部变量,但未加入依赖数组,会导致逻辑错误(React 会给出警告)。
  2. 无限循环执行:依赖数组包含引用类型(如对象、数组),每次渲染引用变化,导致 useEffect 无限执行,需用 useMemo/useCallback 优化。
  3. 忘记清理副作用:未清除定时器、订阅、未完成的请求,会造成内存泄漏或异常行为。
  4. 在副作用中直接修改状态:若未正确设置依赖,可能导致状态更新触发无限循环。
  5. 依赖数组传空却使用外部变量:空依赖数组表示不监听任何变量,若函数内使用了外部状态/属性,需加入依赖。

总结

  1. useEffect 是处理函数组件副作用的核心 Hook,统一管理挂载、更新、卸载逻辑。
  2. 依赖数组是控制执行时机的关键:空数组仅挂载执行,传依赖项则监听变化,不传则每次渲染都执行。
  3. 必须用清理函数处理定时器、订阅等副作用,避免内存泄漏,同时遵循依赖规则,保证逻辑正确性。
评论数量:0