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: 没有清理函数,不执行任何操作
相关推荐
zwjapple4 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20206 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem7 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊7 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing7 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止7 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall7 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴7 小时前
简单入门Python装饰器
前端·python
袁煦丞8 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作