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: 没有清理函数,不执行任何操作
相关推荐
望获linux6 分钟前
【Linux基础知识系列:第一百五十九篇】磁盘健康监测:smartctl
linux·前端·数据库·chrome·python·操作系统·软件
十一吖i1 小时前
vue3表格显示隐藏列全屏拖动功能
前端·javascript·vue.js
冰暮流星3 小时前
css之线性渐变
前端·css
徐同保3 小时前
tailwindcss暗色主题切换
开发语言·前端·javascript
mapbar_front3 小时前
大厂精英为何在中小公司水土不服?
前端
生莫甲鲁浪戴3 小时前
Android Studio新手开发第二十七天
前端·javascript·android studio
2501_916008895 小时前
Web 前端开发常用工具推荐与团队实践分享
android·前端·ios·小程序·uni-app·iphone·webview
SkylerHu6 小时前
前端代码规范:husky+ lint-staged+pre-commit
前端·代码规范
菜鸟una6 小时前
【微信小程序 + 消息订阅 + 授权】 微信小程序实现消息订阅流程介绍,代码示例(仅前端)
前端·vue.js·微信小程序·小程序·typescript·taro·1024程序员节
Yeats_Liao6 小时前
Go Web 编程快速入门 05 - 表单处理:urlencoded 与 multipart
前端·golang·iphone