React Hooks特性之useEffect解析

前言

在 React 的函数式组件开发中,useEffect 是一个极其重要的 Hook。它让我们能够在函数组件中处理副作用操作,如数据获取、订阅、手动 DOM 操作等。

React Hooks 基础回顾

在深入 useEffect 之前,我们先简单回顾一下 React Hooks 的基本概念。Hooks 是 React 16.8 引入的新特性,允许我们在不编写 class 组件的情况下使用 state 和其他 React 特性。

useEffect 是 Hooks 中最核心的一个,用于处理副作用操作。

useEffect深度解析

类型 示例 是否属于副作用
纯函数 const sum = (a, b) => a + b ❌ 无副作用
副作用操作 fetch(), setTimeout() ✅ 有副作用
状态更新 setState() ✅ 有副作用

基本概念

useEffect是React专门用于处理副作用的Hook,它能够:

  • 在组件渲染后执行副作用操作
  • 控制副作用的执行时机
  • 提供清理机制避免内存泄漏

副作用类型分析

执行机制详解

useEffect的执行可以分为三个阶段:

  1. ​挂载阶段​

    • 组件首次渲染后执行
    • 类似于类组件的componentDidMount
  2. ​更新阶段​

    • 依赖项变化时重新执行
    • 类似于类组件的componentDidUpdate
  3. ​卸载阶段​

    • 组件卸载时执行清理函数
    • 类似于类组件的componentWillUnmount

实战应用模式

模式一:持续执行的副作用

当不需要控制执行时机时,可以省略依赖数组:

jsx 复制代码
useEffect(() => {
  console.log('每次渲染后都会执行');
});

模式二:单次执行的副作用

通过空依赖数组控制只在挂载时执行:

jsx 复制代码
useEffect(() => {
  console.log('仅在组件挂载时执行');
  return () => {
    console.log('组件卸载时执行清理');
  };
}, []);

模式三:条件执行的副作用

通过指定依赖项控制执行时机:

jsx 复制代码
const [count, setCount] = useState(0);

useEffect(() => {
  console.log(`count变为: ${count}`);
  document.title = `当前计数: ${count}`;
}, [count]);

模式四:需要清理的副作用

当副作用涉及资源管理时,必须提供清理函数:

jsx 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('定时器运行中');
  }, 1000);
  
  return () => {
    clearInterval(timer);
  };
}, []);

综合例子解析

下面通过一个完整的组件示例,展示useEffect的四种使用模式:

jsx 复制代码
import React, { useState, useEffect } from 'react';

function EffectDemo() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState('初始数据');
  const [width, setWidth] = useState(window.innerWidth);

  // 模式1: 持续执行
  useEffect(() => {
    console.log('【模式1】每次渲染后执行');
  });

  // 模式2: 单次执行
  useEffect(() => {
    console.log('【模式2】组件挂载时执行');
    return () => {
      console.log('【模式2】组件卸载时清理');
    };
  }, []);

  // 模式3: 条件执行
  useEffect(() => {
    console.log(`【模式3】count变为: ${count}`);
    document.title = `当前计数: ${count}`;
    return () => {
      console.log('【模式3】清理前一次effect');
    };
  }, [count]);

  // 模式4: 需要清理
  useEffect(() => {
    console.log('【模式4】设置窗口监听器');
    const handleResize = () => {
      setWidth(window.innerWidth);
      console.log('窗口大小变化:', window.innerWidth);
    };
    window.addEventListener('resize', handleResize);
    
    return () => {
      console.log('【模式4】移除窗口监听器');
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc' }}>
      <h2>useEffect四种模式示例</h2>
      {/* 各模式展示区域 */}
    </div>
  );
}

组件渲染流程分析

让我们分析一下组件在不同情况下的行为:

1. 组件首次挂载

  1. 执行所有useEffect钩子(按照声明顺序)

    • 模式1: 立即执行,控制台输出"【模式1】每次渲染后执行"
    • 模式2: 立即执行,控制台输出"【模式2】组件挂载时执行"
    • 模式3: 立即执行(因为count初始值变化被视为"变化"),控制台输出"【模式3】count变为: 0",设置文档标题
    • 模式4: 立即执行,控制台输出"【模式4】设置窗口监听器",添加resize事件监听器

2. 用户点击增加count按钮

  1. setCount触发状态更新

  2. 组件重新渲染

  3. 执行所有useEffect钩子:

  • 模式1: 再次执行,控制台输出"【模式1】每次渲染后执行"

  • 模式2: 不执行(依赖数组为空且不是首次渲染)

  • 模式3:

    先执行上一次effect的清理函数(如果有),控制台输出"【模式3】清理前一次effect",

    然后执行新的effect,控制台输出"【模式3】count变为: 新值",更新文档标题。

  • 模式4: 不执行(依赖数组为空且不是首次渲染)

3. 调整浏览器窗口大小

  1. 浏览器触发resize事件

  2. 模式4中的handleResize函数被执行:

  • 更新width状态
  • 控制台输出"窗口大小变化: 新宽度"
  1. 状态更新触发组件重新渲染

  2. 执行所有useEffect钩子:

  • 模式1: 再次执行,控制台输出"【模式1】每次渲染后执行"
  • 模式2: 不执行
  • 模式3: 不执行(count没有变化)
  • 模式4: 不执行(依赖数组为空且不是首次渲染)

4. 组件卸载

React调用所有effect的清理函数(按照与声明相反的顺序):

  • 模式4: 执行清理函数,控制台输出"【模式4】移除窗口监听器"
  • 模式3: 执行清理函数,控制台输出"【模式3】清理前一次effect"
  • 模式2: 执行清理函数,控制台输出"【模式2】组件卸载时清理"
  • 模式1: 没有清理函数,不执行任何操作
相关推荐
kyriewen8 小时前
Anthropic 估值逼近万亿美元,Claude Sonnet 5 + Claude Science 一天两连发
前端·ai编程·claude
小徐_233310 小时前
Wot UI 2.2.0 发布:Button 新增 subtle,VideoPreview 预览体验继续增强
前端·微信小程序·uni-app
天蓝色的鱼鱼12 小时前
关于 CSS 你可能不知道的属性,但关键时刻很有用
前端·css
泯泷13 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
妙码生花13 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
泯泷13 小时前
第 1 篇:从 1 + 2 开始:亲手写出第一台 JSVM
前端·javascript·安全
团团崽_七分甜13 小时前
Spring Boot 核心知识点总结
前端
lichenyang45313 小时前
从一个按钮开始,理解 ASCF 框架到底在做什么
前端
古夕13 小时前
第三方 SSO 接入实践:redirect_uri 编码、回调一致性与跨项目联调
前端·vue.js