react useEffect Hook详解

什么是 useEffect Hook?

useEffect 是 React 中的一个 Hook,用于在函数组件中处理副作用(side effects)。副作用是指那些不在组件渲染过程中直接发生的事情,比如:

  • 发起网络请求(比如从服务器获取数据)
  • 操作 DOM(比如改变页面标题)
  • 设置定时器(比如 setTimeout 或 setInterval)
  • 订阅外部数据源(比如 WebSocket)

简单来说,useEffect 让你在组件渲染后(或某些特定时机)执行一些额外的操作。

useEffect 的基本语法

useEffect 是一个函数,接收两个参数:

  1. 回调函数:定义你要执行的副作用逻辑。
  2. 依赖数组(可选):控制副作用何时运行。

基本结构如下:

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // 这里写副作用代码
    console.log('副作用执行了!');
  }, [/* 依赖项 */]);

  return <div>我的组件</div>;
}
  • 回调函数:每次组件渲染后(或依赖项变化时),这里面的代码会运行。
  • 依赖数组:告诉 React 什么时候需要重新运行回调函数。如果不传依赖数组,副作用每次渲染都会运行。

useEffect 的三种运行时机

根据依赖数组的不同,useEffect 的运行时机有以下三种情况:

1. 没有依赖数组

如果不提供依赖数组,副作用会在每次组件渲染后都运行。

useEffect(() => {
  console.log('组件每次渲染都会运行我!');
});

适用场景:很少用,因为每次渲染都运行副作用可能会导致性能问题。

2. 空依赖数组 []

如果提供一个空的依赖数组,副作用只在组件首次渲染后运行一次,类似于类组件中的 componentDidMount。

useEffect(() => {
  console.log('我只在组件首次渲染时运行!');
}, []);

适用场景

  • 初始化时获取数据(比如从 API 加载数据)
  • 设置一次性的事件监听器

3. 有依赖项的数组 [dep1, dep2, ...]

如果依赖数组中有变量,副作用会在组件首次渲染以及依赖项发生变化时运行。

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    console.log(`count 变了,现在是 ${count}`);
  }, [count]);

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

运行逻辑

  • 首次渲染时,useEffect 运行。
  • 每次 count 变化时,useEffect 再次运行。
  • 如果 count 没变,即使组件重新渲染,useEffect 也不会运行。

适用场景

  • 根据状态或 props 变化执行副作用(比如根据用户 ID 获取用户信息)

useEffect 的清理函数

有时候,副作用需要清理,比如清除定时器、取消网络请求或移除事件监听器。useEffect 的回调函数可以返回一个清理函数,在组件卸载或副作用重新运行前执行。

useEffect(() => {
  const timer = setInterval(() => {
    console.log('计时器运行中...');
  }, 1000);

  // 返回清理函数
  return () => {
    clearInterval(timer);
    console.log('计时器被清理了!');
  };
}, []);

清理函数的运行时机

  • 组件卸载时(类似于 componentWillUnmount)。
  • 如果有依赖数组,依赖项变化导致副作用重新运行前,清理函数会先执行。

适用场景

  • 清除定时器(setTimeout、setInterval)
  • 取消订阅(比如 WebSocket 或事件监听器)
  • 防止内存泄漏

常见使用场景

以下是一些 useEffect 的典型例子,帮助你理解它的实际用途。

1. 获取数据(API 请求)

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    fetch('https://api.example.com/users')
      .then(response => response.json())
      .then(data => setUsers(data));
  }, []); // 空数组,只在首次渲染时请求

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. 动态更新页面标题

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `你点击了 ${count} 次`;
  }, [count]); // 依赖 count,每次 count 变化更新标题

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

3. 设置事件监听器

import { useEffect } from 'react';

function WindowSize() {
  useEffect(() => {
    const handleResize = () => {
      console.log(`窗口大小: ${window.innerWidth} x ${window.innerHeight}`);
    };

    window.addEventListener('resize', handleResize);

    // 清理事件监听器
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 空数组,只在首次渲染时设置监听器

  return <div>调整窗口大小试试!</div>;
}

注意事项(新手常见坑)

  1. 不要在 useEffect 中直接修改状态,可能导致无限循环

    useEffect(() => {
      setCount(count + 1); // 错误!会导致无限渲染
    }, [count]);
    

    解决办法:确保状态更新有条件控制,或者使用空依赖数组。

  2. 依赖数组要包含所有用到的变量 如果 useEffect 里的代码用到了某个状态或 props,但没在依赖数组中声明,可能会导致 bug。ESLint 的 react-hooks/exhaustive-deps 规则会帮你检查。

    useEffect(() => {
      console.log(count); // count 必须在依赖数组中
    }, [count]); // 正确
    
  3. 清理函数的重要性 如果不清理定时器或事件监听器,可能会导致内存泄漏,尤其是在组件频繁挂载/卸载时。

  4. 不要把 useEffect 当成普通函数 useEffect 是为副作用设计的,不要用它来处理渲染逻辑(比如直接修改 DOM 或计算值)。渲染逻辑应该放在组件函数体或 useMemo 中。

总结

  • useEffect 的作用:处理副作用,比如数据获取、DOM 操作、事件监听等。
  • 运行时机
    • 无依赖数组:每次渲染都运行。
    • 空数组 []:仅首次渲染运行。
    • 有依赖项 [dep1, dep2]:首次渲染 + 依赖项变化时运行。
  • 清理函数:返回一个函数来清理副作用,防止内存泄漏。
  • 常见场景:API 请求、更新标题、设置定时器或事件监听器。
posted @ 2025-09-27 18:05  牛奔  阅读(19)  评论(0)    收藏  举报