前言:接触 React 函数组件的开发者,最先接触、也最容易混淆的三个 Hooks 就是 useState、useEffect、useRef

很多人只会机械套用:存数据用 useState、发请求用 useEffect、拿 DOM 用 useRef,但始终分不清三者的核心逻辑,导致遇到闭包陷阱、重复渲染、内存泄漏等问题时无从下手。

今天这篇博客,从核心定义、渲染机制、使用场景、代码实战、易错点、终极对比六个维度,一次性讲透三者的区别,帮你彻底告别混淆,精准落地业务开发。

一、核心一句话极简区分

先记住三句核心口诀,搞定 80% 的使用场景:

  • useState:管理响应式视图状态,数据更新 → 组件重新渲染,页面视图刷新

  • useEffect:处理组件副作用,在渲染完成后执行异步、DOM、事件等额外逻辑

  • useRef:存储非响应式可变数据,数据更新不触发渲染,专属 DOM 操作、临时变量存储

二、逐个拆解:原理+实战用法

1. useState:组件的「响应式状态仓库」

在 React 函数组件中,普通变量无法驱动视图更新,useState 就是专门用来定义「需要展示在页面上、需要视图联动更新」的数据

核心特性
  • 通过 setXXX 函数更新状态,更新必触发组件重渲染

  • 状态更新为异步批量更新,多次连续更新会合并执行,优化性能

  • 每次渲染都会生成独立的状态快照,是闭包陷阱的主要来源

  • 仅用于驱动视图变化的核心数据

实战代码示例
import { useState } from 'react'

export default function CountDemo() {
  // 定义响应式状态
  const [count, setCount] = useState(0)

  const handleAdd = () => {
    // 状态更新,触发组件重渲染,页面数字刷新
    setCount(count + 1)
    // 函数式更新,规避异步更新、闭包问题(推荐)
    // setCount(prev => prev + 1)
  }

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={handleAdd}>点击+1</button>
    </div>
  )
}
适用场景

所有需要页面实时更新展示的数据:计数器、表单输入值、弹窗显隐、列表数据、开关状态、加载状态等。

2. useEffect:组件的「副作用处理器」

React 组件的核心逻辑是渲染 UI,所有和 UI 渲染无关的逻辑,都统称为「副作用」,而 useEffect 就是专门用来统一管理副作用的钩子。

核心特性
  • 执行时机:渲染完成后异步执行,不阻塞页面渲染

  • 依赖数组控制执行逻辑,精准适配组件生命周期

  • 支持清理函数,可解决定时器、事件监听的内存泄漏问题

三种执行规则(高频考点)
  1. 无依赖:组件每次渲染后都执行

  2. 空依赖 []:组件挂载时执行一次(模拟 componentDidMount)

  3. 带依赖:仅依赖项发生变化时执行

实战代码示例(定时器+清理)
import { useState, useEffect } from 'react'

export default function TimerDemo() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    // 开启定时器(副作用)
    const timer = setInterval(() => {
      setCount(prev => prev + 1)
    }, 1000)

    // 清理函数:组件卸载 / 副作用重新执行前触发
    return () => clearInterval(timer)
  }, []) // 空依赖:挂载执行一次,卸载清理

  return <p>自动计数:{count}</p>
}
适用场景

接口请求、定时器/延时器、全局事件监听(滚动、点击)、DOM 手动操作、数据订阅、第三方库初始化等。

3. useRef:组件的「永久缓存容器」

很多开发者误以为 useRef 只能操作 DOM,其实它的核心本质是:创建一个组件生命周期内永久不变的引用对象

它是 React 中唯一可以存可变数据、更新不触发渲染的钩子,相当于组件的「私有临时仓库」。

核心两大用途
  1. 操作真实 DOM:获取节点、聚焦、获取宽高、滚动定位

  2. 存储临时变量:定时器 ID、上一次状态、防抖节流变量、实例对象

核心特性
  • 修改 ref.current绝对不会触发组件重渲染

  • 整个组件生命周期内,引用地址唯一,不会随渲染重置

  • 不受闭包陷阱影响,始终可以获取最新的值

实战代码示例
import { useRef, useEffect } from 'react'

export default function RefDemo() {
  // 创建ref容器
  const inputRef = useRef(null)
  const timerRef = useRef(null)

  // 挂载后自动聚焦输入框
  useEffect(() => {
    inputRef.current?.focus()
    // 存储定时器ID,用于后续清理
    timerRef.current = setTimeout(() => {
      console.log('定时器执行')
    }, 2000)

    // 清理定时器
    return () => clearTimeout(timerRef.current)
  }, [])

  return <input ref={inputRef} placeholder="挂载自动聚焦" />
}
适用场景

DOM 元素操作、保存定时器/请求标识、记录历史状态、防抖节流缓存、不需要视图更新的临时数据。

三、三者核心区别对照表

Hook

核心用途

更新是否重渲染

生命周期特点

核心痛点

useState

管理视图响应式状态

✅ 触发重渲染

每次渲染生成新快照

存在闭包陷阱、异步批量更新

useEffect

处理组件各类副作用

不主动渲染,依赖变化触发执行

挂载/更新/卸载三阶段执行

依赖写错易导致死循环、内存泄漏

useRef

DOM操作、缓存临时变量

❌ 不触发重渲染

全局唯一引用,贯穿生命周期

更新无视图响应,需配合useState更新页面

四、高频易错点避坑指南

1. useState 避坑

  • 不要连续多次调用 setState,会被批量合并,建议使用函数式更新setX(prev => prev + 1)

  • 状态是快照,闭包中拿到的是旧渲染的值,不是最新值

2. useEffect 避坑

  • 必须严格补齐依赖项,遗漏依赖会导致逻辑失效、数据陈旧

  • 定时器、事件监听、订阅必须写清理函数,否则造成内存泄漏

  • 禁止在 useEffect 中无限制更新 state,会造成无限渲染循环

3. useRef 避坑

  • 修改 ref.current 页面不会变化,想要视图更新必须搭配 useState

  • DOM 操作必须在 useEffect 中执行,渲染阶段无法获取 DOM

  • 不要用 ref 存储需要视图联动的数据,违背 React 响应式思想

五、三者结合实战完整案例

下面代码整合三个钩子,实现「输入框聚焦 + 自动计数 + 缓存最新值」完整功能:

import { useState, useEffect, useRef } from 'react'

export default function AllHookDemo() {
  // 响应式状态:驱动视图更新
  const [count, setCount] = useState(0)
  // ref缓存:保存上一次的count值,不触发渲染
  const prevCountRef = useRef(0)
  // DOM ref:操作输入框
  const inputRef = useRef(null)

  // 副作用:页面挂载后聚焦输入框
  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  // 副作用:count变化时更新缓存值
  useEffect(() => {
    prevCountRef.current = count
  }, [count])

  return (
    <div style={{ padding: '20px' }}>
      <p>当前计数:{count}</p>
      <p>上一次计数:{prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>点击+1</button>
      <br/>
      <input ref={inputRef} placeholder="挂载自动聚焦" style={{ marginTop: '10px' }}/>
    </div>
  )
}

六、最后总结(开发选型准则)

开发中不用纠结,直接按这套规则选型即可:

  1. 需要页面跟着数据变 → 用 useState

  2. 需要页面渲染后执行额外逻辑 → 用 useEffect

  3. 需要操作 DOM / 存不更新视图的临时数据 → 用 useRef

三个钩子各司其职:useState 管视图、useEffect 管逻辑、useRef 管缓存,理解底层渲染机制,就能彻底告别滥用问题。

更多推荐