React Hooks 详解:useState 与 useEffect 的使用与核心概念

93 阅读7分钟

React Hooks 详解:useState 与 useEffect 的使用与核心概念

在 React 的发展历程中,Hooks 的出现彻底改变了函数组件的能力。以use开头的 React Hooks,让函数组件能够轻松管理状态、处理副作用,实现了与类组件同等的功能,同时保持了代码的简洁与原生 JavaScript 风格。本文将结合实际代码,详细解析useStateuseEffect这两个最核心的内置 Hooks。

一、React Hooks 概述

React Hooks 是 React 提供的一系列以use为前缀的函数 API,其核心作用是让函数组件能够使用状态(State)和其他 React 特性(如生命周期、上下文等)。与类组件相比,Hooks 使代码更简洁、逻辑更清晰,同时避免了类组件中this指向、生命周期嵌套等问题。

从本质上讲,Hooks 是对 React 特性的 “函数式封装”,让开发者可以用更接近原生 JavaScript 的方式编写 React 组件。本文重点讲解两个最基础的内置 Hooks:useState(状态管理)和useEffect(副作用处理)。

二、useState:管理组件的响应式状态

useState是 React 中用于管理组件状态的 Hook,它让函数组件拥有了 “记忆” 能力,能够保存并更新数据,实现页面的响应式渲染。

1. 基本用法与初始化

useState的基本语法如下:

const [state, setState] = useState(initialValue);
  • state:当前的状态值,初始值由initialValue决定
  • setState:更新状态的函数,调用后会触发组件重新渲染
  • initialValue:状态的初始值,可以是任意类型(基本类型、对象、数组等)

代码示例

import { useState } from 'react';

export default function App() {
  // 用函数初始化复杂计算的初始值
  const [num, setNum] = useState(() => {
    const num1 = 1 + 2;
    const num2 = 2 + 3;
    return num1 + num2; // 初始值为6
  });
  // ...
}

这里有一个重要细节:当初始值需要通过复杂计算得到时,推荐将initialValue写成一个纯函数(相同输入始终返回相同输出,无副作用)。这样的函数只会在组件初始化时执行一次,避免每次渲染都重复计算,提高性能。

2. 状态更新的两种方式

setState更新状态有两种方式,分别适用于不同场景:

(1)直接传入新值

当新状态不依赖于旧状态时,可以直接传入新值:

<div onClick={() => setNum(prevNum => prevNum + 1)}>{num}</div>
(2)传入更新函数(依赖旧状态时)

当新状态需要基于旧状态计算时,必须传入一个函数,该函数的参数为 “上一次的状态”(prevState):

<div onClick={() => setNum((prevNum) => {
  console.log(prevNum + 1); 
  return prevNum + 1; // 基于旧值计算新值
})}>{num}</div>

这种方式的核心作用是避免状态更新的竞态问题。由于 React 的状态更新可能是异步的,直接使用num + 1可能获取到的是未更新的旧值,而通过prevState可以确保拿到的是最新的状态。

3. 注意事项

  • useState的初始值仅在组件第一次渲染时生效,后续渲染会忽略初始值
  • 初始值不能是异步操作的结果(如异步请求),因为状态必须是 “确定的”,而异步操作的结果无法保证同步获取
  • setState是 “替换” 而非 “合并” 状态(与类组件的this.setState不同),如果状态是对象,需要手动合并旧属性(如setUser(prev => ({ ...prev, name: 'new' }))

三、useEffect:处理组件的副作用

在 React 中,组件本身应该是 “纯函数”—— 即输入props后,输出固定的 JSX,不产生副作用。但实际开发中,我们需要处理数据请求、定时器、事件监听等 “副作用”(Side Effect),useEffect正是用于管理这些副作用的 Hook。

1. 基本语法与执行时机

useEffect的语法如下:

useEffect(() => {
  // 副作用逻辑(如数据请求、定时器等)
  
  return () => {
    // 清理函数(可选):用于清除副作用(如清除定时器、取消订阅等)
  };
}, [dependencies]); // 依赖项数组(可选)

useEffect的执行时机由第二个参数dependencies(依赖项数组)决定,主要分为三种情况:

(1)依赖项为空数组[]:仅在组件挂载时执行(模拟onMounted

当依赖项为空数组时,useEffect只会在组件第一次渲染完成后执行一次,相当于 Vue 中的onMounted生命周期。

代码示例

useEffect(() => {
  console.log('123123'); // 组件挂载时执行
  const timer = setInterval(() => {
    console.log('timer');
  }, 1000);

  // 清理函数:组件卸载时执行
  return () => {
    console.log('remove');
    clearInterval(timer); // 清除定时器,避免内存泄漏
  };
}, []); // 空依赖项:仅挂载时执行

这里的返回值是一个清理函数,它会在组件卸载前执行,用于清理副作用(如清除定时器、取消事件监听等),避免内存泄漏。

(2)依赖项为非空数组[state1, state2]:挂载时执行 + 依赖项变化时执行

当依赖项数组包含状态或变量时,useEffect会在组件挂载时执行一次,之后每当数组中的任意依赖项发生变化时,都会重新执行。

代码示例

// 当num变化时执行
useEffect(() => {
  console.log(num, 'zzz'); // 挂载时执行一次,num更新时再次执行
}, [num]); // 依赖num:num变化则触发

这种方式常用于 “监听状态变化”,例如当某个状态更新后,执行对应的逻辑(如根据新的num重新请求数据)。

(3)无依赖项:每次渲染后都执行

如果不传入第二个参数,useEffect会在每次组件渲染完成后执行(包括初始挂载和每次更新)。

代码示例

// 无依赖项:每次渲染(挂载+更新)后都执行
useEffect(() => {
  console.log('ddd');
 })

这种方式需谨慎使用,频繁执行可能导致性能问题。

2. 清理函数的作用

useEffect的返回值(清理函数)有两个核心作用:

  1. 在下次执行副作用前清理上一次的副作用:例如当依赖项变化时,先清除旧的定时器,再创建新的定时器。

代码示例

useEffect(() => {
  console.log('effect');
  const timer = setInterval(() => {
    console.log(num); // 依赖num的定时器
  }, 1000);

  return () => {
    console.log('remove');
    clearInterval(timer); // 下次执行effect前,先清除上一个定时器
  };
}, [num]); // 依赖num:num变化时,先执行清理函数,再执行新的effect

num变化时,React 会先调用清理函数清除旧定时器,再执行新的副作用创建新定时器,避免多个定时器同时运行。

  1. 组件卸载时清理副作用:例如组件被销毁时,清除定时器、取消网络请求等,防止内存泄漏。

3. 常见副作用场景

useEffect适用于所有 “非纯函数” 操作,常见场景包括:

  • 数据请求(如 App.jsx 中的queryData
  • 定时器 / 间隔器(setTimeout/setInterval
  • 事件监听(addEventListener
  • DOM 操作(如手动修改 DOM 样式)

四、核心概念总结

  1. 纯函数与副作用

    • 纯函数:输入固定时输出固定,无外部影响(如组件根据propsstate输出 JSX)
    • 副作用:影响组件外部环境或需要清理的操作(如数据请求、定时器等),由useEffect管理
  2. 依赖项数组

    • 决定useEffect的执行时机,是性能优化的关键
    • 必须包含所有在副作用中使用的状态 / 变量,否则可能因 “闭包陷阱” 获取到旧值
  3. 闭包特性

    • useEffect的副作用函数和清理函数会形成闭包,捕获当前渲染周期的状态值
    • 当依赖项更新时,会创建新的闭包,从而获取最新的状态

五、总结

  • useState让函数组件拥有了响应式状态,支持基于旧状态的更新,初始化时可通过纯函数处理复杂计算
  • useEffect统一管理副作用,通过依赖项数组控制执行时机,清理函数避免内存泄漏