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

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


一、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(手动聚焦输入框)
  • 设置定时器(setTimeout、setInterval)
  • 日志记录等

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 让函数组件更加强大和灵活,理解并掌握 useState 和 useEffect,将显著提升你的 React 开发效率和代码质量。

相关推荐
何何____10 分钟前
TypeScript 函数类型兼容性
前端·javascript·typescript
雪芽蓝域zzs21 分钟前
json数据转化成interface属性
前端·javascript·json
鬼手点金1 小时前
ReAct 循环
linux·服务器·前端·javascript·react.js·前端框架
雪芽蓝域zzs1 小时前
Vue导入方法三种方式对比
前端·javascript·vue.js
VimorphTech3 小时前
React 任务轮询,为什么不能只写一个 setInterval?
javascript·typescript
lexiangqicheng3 小时前
Docker的沙箱机制是怎样的?
前端·javascript
LCG米5 小时前
STM32 HardFault故障定位实测:压栈帧寄存器解析、CFSR故障源判读与出错地址回溯
前端·javascript·stm32·单片机·嵌入式
持敬chijing5 小时前
白盒测试与代码审计-JS 数据逆向
javascript·网络·安全·web安全·网络安全·网络攻击模型·安全威胁分析
薛一半6 小时前
Redux实战:组件状态管理全解析
开发语言·前端·javascript·react.js