# React Hooks 全面解析:从 useState 到 useEffect,掌握状态与副作用管理

React 16.8 推出了革命性的 Hooks 特性,让函数组件拥有了管理状态和处理副作用的能力。相比于传统的类组件,Hooks 提供了更简洁、更灵活的函数式编程体验。本文将重点介绍两个核心 Hooks ------ useStateuseEffect,并结合组件生命周期,详细讲解如何高效管理状态和副作用。


一、useState ------ 函数组件的状态管理利器

useState 是 React 用来在函数组件中声明状态的 Hook。它让函数组件拥有了类似类组件中 this.state 的功能,但写法更简洁。

基本用法

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

function Counter() {
  // 声明一个叫 count 的状态变量,初始值为 0
  const [count, setCount] = useState(0);

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

export default Counter;

说明

  • useState 返回一个数组,第一个元素是当前状态,第二个是更新状态的函数。
  • 每次调用 setCount,组件会重新渲染,显示最新状态。

二、useEffect ------ 副作用处理的"守护神"

在 React 中,副作用指的是除了渲染以外需要执行的操作,比如数据请求、DOM 操作、事件绑定等。

useEffect 用于在函数组件中处理这些副作用,等效于类组件中的生命周期函数。


1. useEffect 是什么?

useEffect 是 React Hooks 中专门用来处理"副作用"的函数。副作用(side effect)是指那些影响到组件之外的操作,例如:

  • 网络请求(fetch 数据)
  • 订阅事件(如监听滚动、WebSocket)
  • 操作 DOM(手动聚焦输入框)
  • 设置定时器(setTimeoutsetInterval
  • 日志记录等

React 渲染本身是"纯粹的",但现实中我们不可避免需要执行这些副作用,而 useEffect 正是帮我们优雅管理它们的工具。


2. useEffect 的基本语法

scss 复制代码
useEffect(() => {
  // 副作用代码

  return () => {
    // 清理副作用代码(可选)
  };
}, [依赖项]);
  • 第一个参数是一个副作用函数(effect function),里面写需要执行的副作用代码。
  • 第二个参数是"依赖项数组",用来告诉 React 什么时候重新执行这个副作用。

3. useEffect 的执行时机

  • 组件挂载后
    useEffect 会在组件第一次渲染到页面之后执行。它的执行时机是在浏览器绘制完成之后,因此不会阻塞页面渲染。
  • 依赖项变化后
    如果你传入了依赖项数组,那么只有当数组中的某个依赖发生变化时,useEffect 中的副作用才会重新执行。
  • 组件卸载前
    useEffect 中可以返回一个"清理函数",React 会在组件卸载时调用这个清理函数,以便释放资源、防止内存泄漏。

4. 依赖项数组详解

  • 不传依赖项数组

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

    每次组件渲染(包括首次和更新)后,都会执行副作用函数。

  • 传空数组 []

    scss 复制代码
    useEffect(() => {
      console.log('只在组件挂载时执行一次');
    }, []);

    只会在组件挂载时执行一次,类似于类组件的 componentDidMount

  • 传有依赖的数组

    scss 复制代码
    useEffect(() => {
      console.log('依赖 count,count 变化时执行');
    }, [count]);

    仅在依赖发生变化时执行副作用。


5. 清理函数(Cleanup)

useEffect 允许返回一个函数,这个函数会在以下时机被调用:

  • 组件卸载前(类似 componentWillUnmount
  • 下次副作用执行前(如果依赖项发生变化)

清理函数常用来:

  • 清除定时器
  • 取消事件监听
  • 取消网络请求(配合 AbortController)
  • 取消订阅
javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('计时器执行');
  }, 1000);

  return () => {
    clearInterval(timer); // 清理定时器,避免内存泄漏
  };
}, []);

6. useEffect 不能直接写 async 的原因

useEffect 的回调函数不允许是异步函数,因为异步函数会返回 Promise,而 React 期望副作用函数返回清理函数或 undefined

错误示例:

dart 复制代码
useEffect(async () => {
  const res = await fetch('/api');
  // 错误:useEffect 不接受 async 函数
});

正确写法:

scss 复制代码
useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api');
    // 处理数据
  }

  fetchData();
}, []);

7. 常见问题与注意事项

  • 依赖项遗漏问题

    如果依赖项数组中没有包含所有用到的外部变量,可能导致副作用函数中使用的变量不是最新值,容易出现 bug。

    React 官方 ESLint 插件会提醒你补全依赖。

  • 避免无限循环

    如果副作用中更新了状态,且状态是依赖项,会导致副作用无限执行。需要合理设计依赖项,或通过条件判断避免。

  • 网络请求的取消

    组件卸载时未取消请求,可能导致内存泄漏和更新已卸载组件的错误。配合 AbortController 实现取消:

    ini 复制代码
    useEffect(() => {
      const controller = new AbortController();
    
      fetch('/api/data', { signal: controller.signal })
        .then(res => res.json())
        .then(data => {
          // 更新状态
        })
        .catch(err => {
          if (err.name === 'AbortError') {
            console.log('请求被取消');
          }
        });
    
      return () => {
        controller.abort(); // 取消请求
      };
    }, []);

三、组件生命周期与 Hooks 对应关系

生命周期阶段 类组件函数 Hooks 方式
挂载后 (mounted) componentDidMount useEffect(() => {}, [])
更新后 (updated) componentDidUpdate useEffect(() => {}, [依赖])
卸载时 (unmounted) componentWillUnmount useEffect 返回清理函数

四、组件请求接口的最佳实践

  • 什么时候请求?
    组件挂载后发起请求,确保界面快速展示数据。
  • 怎么请求?
    将请求逻辑放入 useEffect 中,避免阻塞渲染。
  • 避免多次请求
    设置依赖项为空数组 [],保证请求只执行一次。
javascript 复制代码
import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      const res = await fetch(`https://api.example.com/users/${userId}`);
      const data = await res.json();
      setUser(data);
    }

    fetchUser();

    return () => {
      // 这里可以做一些清理操作,比如取消请求等
      console.log('组件卸载或 userId 变更,进行清理');
    };
  }, [userId]);

  if (!user) return <div>加载中...</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

export default UserProfile;

五、总结

  • useState 简化了状态管理,赋予函数组件状态能力。
  • useEffect 专注于副作用,涵盖挂载、更新和卸载时机。
  • 请求接口时,建议放在 useEffect,且依赖项为空数组,确保只请求一次。
  • useEffect 中不能直接写 async,需在内部声明异步函数。
  • 一定要合理清理副作用,避免内存泄漏。
  • 理解依赖数组机制,避免遗漏或无限循环。

React Hooks 让函数组件更加强大和灵活,理解并掌握 useStateuseEffect,将显著提升你的 React 开发效率和代码质量。

相关推荐
像风一样自由20202 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
浪裡遊3 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
Liudef065 小时前
2048小游戏实现
javascript·css·css3
独立开阀者_FwtCoder7 小时前
【Augment】 Augment技巧之 Rewrite Prompt(重写提示) 有神奇的魔法
前端·javascript·github
我想说一句7 小时前
事件机制与委托:从冒泡捕获到高效编程的奇妙之旅
前端·javascript
汤姆Tom7 小时前
JavaScript reduce()函数详解
javascript
小飞悟7 小时前
你以为 React 的事件很简单?错了,它暗藏玄机!
前端·javascript·面试
中微子7 小时前
JavaScript 事件机制:捕获、冒泡与事件委托详解
前端·javascript
蓝翔认证10级掘手8 小时前
🤯 家人们谁懂啊!我的摸鱼脚本它...它成精了!🚀
javascript
前端康师傅8 小时前
JavaScript 中你不知道的按位运算
前端·javascript