React 中 useState、useEffect、useRef 的区别与使用场景详解,终于有人讲明白了

一、先用一句话概括这三个 Hook

如果你现在还很懵,先别慌,先记住下面这三句话。

useState

让组件记住会影响页面展示的数据

useEffect

让组件在渲染后去执行额外操作

useRef

让组件保存一个不会触发重新渲染的值,或者拿到 DOM 元素

这三句话,已经把它们最本质的区别说出来了。

如果还觉得抽象,没关系,接下来我一个个拆开讲。

二、先说 useState:它是"状态管理"的

React 组件最大的特点之一,就是:

数据一变,页面跟着变。

useState,就是专门用来保存这种"会驱动页面变化的数据"的。

先看最经典的例子。

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

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      当前点击了 {count} 次
    </button>
  );
}

export default Counter;

这里这句最关键:

scss 复制代码
const [count, setCount] = useState(0);

它的意思你可以直接翻译成人话:

React,帮我准备一个状态,初始值是 0,当前值叫 count,修改它的方法叫 setCount。

也就是说:

  • count 是当前状态值
  • setCount 是更新状态的方法
  • 0 是初始值

当你点击按钮执行:

scss 复制代码
setCount(count + 1);

React 会做两件事:

  1. 更新状态值
  2. 重新渲染组件

所以页面上的 count 就会变。

useState 最典型的应用场景

useState 常用于这些地方:

  • 计数器数字
  • 输入框内容
  • 弹窗是否显示
  • 下拉框选中项
  • 当前分页页码
  • 列表数据
  • 加载状态 loading
  • 错误提示信息

比如控制弹窗:

scss 复制代码
const [visible, setVisible] = useState(false);

比如保存输入框内容:

scss 复制代码
const [keyword, setKeyword] = useState("");

比如保存接口返回的数据:

ini 复制代码
const [list, setList] = useState([]);

这些都属于:

一旦数据变化,页面就要跟着变化。

这时候就应该用 useState

三、再说 useEffect:它是"副作用处理"的

很多人第一次看到"副作用"这个词,容易被吓到。

其实它没有那么玄乎。

你可以简单把副作用理解成:

除了渲染页面以外,还要额外做的事情。

比如:

  • 请求接口
  • 设置定时器
  • 监听事件
  • 修改浏览器标题
  • 操作本地存储
  • 手动操作 DOM
  • 组件销毁时做清理

这些都不是"渲染 JSX"本身,而是页面渲染之后要顺便做的事。

这时候就轮到 useEffect 出场了。

先看一个最简单的例子:

javascript 复制代码
import React, { useEffect } from "react";

function Demo() {
  useEffect(() => {
    console.log("组件渲染完成了");
  }, []);

  return <div>Hello React</div>;
}

这段代码的意思就是:

页面渲染完以后,执行 console.log

所以你可以理解成:

useEffect = 渲染后执行任务

useEffect 最常见的使用场景

1. 请求接口

scss 复制代码
useEffect(() => {
  fetch("/api/user")
    .then((res) => res.json())
    .then((data) => {
      console.log(data);
    });
}, []);

2. 设置定时器

javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log("每秒执行一次");
  }, 1000);

  return () => clearInterval(timer);
}, []);

3. 监听事件

javascript 复制代码
useEffect(() => {
  const handleResize = () => {
    console.log(window.innerWidth);
  };

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

4. 修改页面标题

ini 复制代码
useEffect(() => {
  document.title = "用户中心";
}, []);

这些都是副作用。

也就是说:

只要不是单纯为了渲染页面,而是渲染后还要做点别的事,大概率就要想到 useEffect。

四、再说 useRef:它是"持久容器"和"DOM 引用"

useRef 是很多初学者最容易迷糊的 Hook。

因为它不像 useState 那么直观,也不像 useEffect 那么容易理解成"执行动作"。

其实 useRef 可以简单理解成两个作用。

作用一:获取 DOM 元素

比如你想让输入框在页面加载后自动获取焦点:

javascript 复制代码
import React, { useEffect, useRef } from "react";

function InputFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="请输入内容" />;
}

export default InputFocus;

这里可以这样理解:

  • useRef(null) 创建一个引用对象
  • inputRef.current 会指向真实的 input DOM
  • 通过 focus() 就可以让输入框聚焦

也就是说:

useRef 可以帮你"拿到页面中的真实元素"。

作用二:保存一个值,但不触发页面重新渲染

这是 useRef 更重要、也更容易被忽略的能力。

比如保存定时器 id:

ini 复制代码
const timerRef = useRef(null);

赋值:

ini 复制代码
timerRef.current = setInterval(() => {
  console.log("running");
}, 1000);

清除:

scss 复制代码
clearInterval(timerRef.current);

这个值会一直保留在组件生命周期里,但它变化时不会导致页面重渲染。

所以你可以把 useRef 理解成:

组件里的一个"小盒子",你可以往里面放东西,它会一直记着,但不会因为盒子里的东西变了就刷新页面。

五、它们三个最大的区别,到底是什么?

这是本文最核心的部分。

我先直接给你一个最重要的结论:

Hook 核心作用 数据变化后会不会触发重新渲染
useState 保存状态
useEffect 执行副作用 本身不是存数据的
useRef 保存引用/持久值 不会

把这张表吃透,你就不容易乱用了。

接下来我一个个解释。

六、useState 和 useRef 的区别,初学者最容易搞混

很多人学到这里时,最大的疑问就是:

既然 useState 能存值,useRef 也能存值,那到底啥时候用谁?

答案非常简单:

需要更新页面的,用 useState

不需要更新页面的,用 useRef

来看例子。

场景 1:页面上要显示这个值

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

function Demo() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>当前数字:{count}</p>
      <button onClick={() => setCount(count + 1)}>加一</button>
    </div>
  );
}

这里 count 是显示在页面上的。

点击按钮后,页面中的数字也要变化。

所以必须用 useState

场景 2:只是内部记一下,不需要显示

javascript 复制代码
import React, { useRef } from "react";

function Demo() {
  const clickTimesRef = useRef(0);

  const handleClick = () => {
    clickTimesRef.current += 1;
    console.log("点击次数:", clickTimesRef.current);
  };

  return <button onClick={handleClick}>点击我</button>;
}

这里点击次数只是打印在控制台,并没有显示在页面上。

那就没必要用 useState,用 useRef 就够了。

再总结一遍

useState 的场景

  • 页面要展示这个数据
  • 数据变化后希望组件重新渲染
  • 数据会驱动 UI 更新

useRef 的场景

  • 只是临时保存一个值
  • 不希望因为这个值变化而重新渲染
  • 保存 DOM、定时器 id、上一次值等

七、为什么 useRef 改了值,页面不更新?

这个问题特别经典,面试也爱问。

比如下面这段代码:

javascript 复制代码
import React, { useRef } from "react";

function Demo() {
  const countRef = useRef(0);

  const handleClick = () => {
    countRef.current += 1;
    console.log(countRef.current);
  };

  return (
    <div>
      <p>{countRef.current}</p>
      <button onClick={handleClick}>点击</button>
    </div>
  );
}

很多初学者会以为点击按钮后,页面上的数字会变。

但实际上,页面大概率不会更新。

为什么?

因为:

修改 ref.current 不会触发组件重新渲染。

React 只会在这些情况下重新渲染组件:

  • props 变了
  • state 变了
  • 父组件重新渲染导致子组件重新渲染

ref.current 的变化,不在 React 的"响应式更新系统"里。

所以它改了,React 不会主动刷新页面。

这就是 useRefuseState 最大的区别之一。

八、useEffect 和 useState 的关系是什么?

开发中经常看到这俩一起出现。

比如页面加载后请求数据:

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

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);

  return (
    <ul>
      {users.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

这里的配合方式非常典型:

  • useState 负责存数据
  • useEffect 负责获取数据

也就是说:

useState 管"保存结果",useEffect 管"执行动作"。

你可以理解成:

  • useState 是仓库
  • useEffect 是工人
  • 工人出去搬货,最后把货放进仓库里

这是它们最经典的协作模式。

九、useEffect 的依赖数组到底怎么理解?

这个问题,是 React 初学者最容易卡壳的地方之一。

我们先看写法:

scss 复制代码
useEffect(() => {
  console.log("执行副作用");
}, []);

第二个参数 [],就叫 依赖数组

它决定这个副作用什么时候执行。

1. 传空数组 []

scss 复制代码
useEffect(() => {
  console.log("只执行一次");
}, []);

表示:

组件首次渲染完成后执行一次。

常见用途:

  • 页面加载请求一次接口
  • 初始化某些逻辑
  • 绑定事件监听并在销毁时清理

2. 不传依赖数组

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

表示:

组件每次渲染后都会执行。

这个一般要慎用,否则可能造成不必要的执行。

3. 传某个依赖项

scss 复制代码
useEffect(() => {
  console.log("count 变化了");
}, [count]);

表示:

首次渲染执行一次,以后只有 count 变化时才执行。

4. 传多个依赖项

scss 复制代码
useEffect(() => {
  console.log("count 或 keyword 变化了");
}, [count, keyword]);

表示:

只要 countkeyword 中任意一个变化,副作用就会重新执行。

最通俗的理解方式

你可以把依赖数组理解成一句话:

只要数组里的这些值变了,就重新执行这段副作用代码。

这就很好记了。

十、useEffect 的清理函数是干嘛的?

很多人刚开始看到这种写法会有点懵:

javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log("执行中");
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);

为什么 useEffect 里面还要 return 一个函数?

这个函数叫:

清理函数

它一般会在这些时候执行:

  1. 组件卸载时
  2. 副作用重新执行前,先清理上一次的副作用

最常见的用途有:

  • 清除定时器
  • 移除事件监听
  • 取消订阅
  • 中断请求

比如监听窗口大小变化:

javascript 复制代码
useEffect(() => {
  const handleResize = () => {
    console.log(window.innerWidth);
  };

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

这里如果不做清理,组件销毁后事件还在,就可能造成内存泄漏或者逻辑混乱。

所以你可以这样记:

副作用用了什么外部资源,离开时就记得清掉。

十一、三个 Hook 的生活化比喻,一下就记住

为了让你更容易记住,我给你打个特别通俗的比方。

把 React 组件想象成一个办公室员工。

useState:员工的记事本

员工需要记住今天要做什么、当前完成多少、按钮是开还是关。

这些会影响工作展示给老板看。

所以:

useState = 会展示出来的正式数据

useEffect:员工的任务清单

员工上班后要做事:

  • 给客户打电话
  • 发邮件
  • 开会
  • 定时汇报

这些不是"展示内容",而是要执行的动作。

所以:

useEffect = 渲染后执行的额外任务

useRef:员工的抽屉

员工抽屉里放着一些东西:

  • 钥匙
  • 工牌
  • 上一次会议记录
  • 某个客户电话
  • 临时编号

这些不需要写到汇报 PPT 上,但又得一直留着备用。

所以:

useRef = 持久保存但不驱动页面变化的数据容器

这个比喻基本能帮很多初学者彻底理顺。

十二、实际开发中该怎么选?

这里我给你一个非常实战的判断口诀。

场景一:数据变了,页面也要变

useState

比如:

  • 输入框输入内容
  • 列表数据变化
  • loading 状态
  • tab 切换
  • 当前选中项

场景二:页面出来后要执行动作

useEffect

比如:

  • 请求接口
  • 绑定事件
  • 启动定时器
  • 修改标题
  • 同步本地存储

场景三:只想记个值,不想刷新页面

useRef

比如:

  • 保存 timer id
  • 保存上一次值
  • 防抖节流中的锁
  • 获取 input DOM
  • 防止重复提交标记

这个口诀非常适合业务开发时快速判断。

十三、一个综合案例,把三个 Hook 串起来理解

下面我们写一个小案例:搜索框自动聚焦,并在输入时同步标题,同时记录输入次数。

ini 复制代码
import React, { useEffect, useRef, useState } from "react";

function SearchDemo() {
  const [keyword, setKeyword] = useState("");
  const inputRef = useRef(null);
  const changeCountRef = useRef(0);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  useEffect(() => {
    document.title = keyword ? `正在搜索:${keyword}` : "搜索页面";
  }, [keyword]);

  const handleChange = (e) => {
    setKeyword(e.target.value);
    changeCountRef.current += 1;
    console.log("输入次数:", changeCountRef.current);
  };

  return (
    <div>
      <h2>搜索示例</h2>
      <input
        ref={inputRef}
        value={keyword}
        onChange={handleChange}
        placeholder="请输入关键词"
      />
      <p>当前关键词:{keyword}</p>
    </div>
  );
}

export default SearchDemo;

这个案例里:

useState

保存输入框内容 keyword

因为它要显示到页面上,所以必须用状态。

第一个 useEffect

页面加载后让输入框自动聚焦

因为这是渲染后执行的动作,所以用 useEffect

第二个 useEffect

每当 keyword 变化时更新浏览器标题

这也属于副作用,所以还是 useEffect

useRef

一个拿 DOM:inputRef

一个记录输入次数:changeCountRef

输入次数只是打印日志,并不展示到页面,所以没必要用 useState,用 useRef 更合适。

这个案例基本把三个 Hook 的职责划分得很清楚了。

十四、面试中怎么回答它们的区别?

如果面试官问你:

useStateuseEffectuseRef 的区别是什么?

你可以这么回答:

useState 主要用于管理组件状态,当状态变化时会触发组件重新渲染,通常用来保存那些会影响页面展示的数据。
useEffect 主要用于处理副作用,也就是组件渲染之后需要执行的额外逻辑,比如请求接口、事件监听、定时器、修改标题等。
useRef 主要用于保存引用或者持久化数据,它既可以获取 DOM 元素,也可以保存一些不需要触发组件重新渲染的值,比如定时器 id、上一次的值等。

它们的核心区别在于:useState 管状态并驱动视图更新,useEffect 管副作用执行,useRef 管持久化引用但不会触发视图更新。

这段话很适合面试时直接说。

十五、初学者最常犯的几个错误

1. 该用 useRef 的地方用了 useState

比如只是存一个定时器 id,却写成:

scss 复制代码
const [timer, setTimer] = useState(null);

其实这类数据不参与页面展示,用 useRef 更合理。

2. 该用 useState 的地方用了 useRef

比如页面上的数字要变化,却写成:

ini 复制代码
const countRef = useRef(0);
countRef.current += 1;

结果发现页面不更新。

因为 useRef 的变化不会触发渲染。

3. 把所有逻辑都往 useEffect 里塞

有些逻辑其实只是普通计算,不一定非要写 useEffect

不要一上来就觉得"只要是逻辑就放 useEffect"。

4. useEffect 依赖数组乱写

比如副作用里明明用到了 count,却不写到依赖数组里,容易造成旧值问题。

5. 忘记清理副作用

比如监听事件、开定时器却不清理,组件销毁后可能引发 bug。

十六、最后给你一个最简单的判断公式

以后开发时,如果你一时分不清到底该用谁,就套这三句判断。

第一问:这个数据要不要显示到页面上?

要,就优先考虑 useState

第二问:这个逻辑是不是要在渲染之后执行?

是,就优先考虑 useEffect

第三问:我是不是只是想记个值,或者拿 DOM,但不想刷新页面?

是,就优先考虑 useRef

这三个问题,基本能帮你解决 80% 的判断场景。

十七、总结

这篇文章讲了很多,其实最后你真正要记住的,就这几句话。

useState 是什么?

保存会影响页面展示的状态,状态变了会重新渲染。

useEffect 是什么?

处理渲染后的副作用,比如请求接口、事件监听、定时器等。

useRef 是什么?

保存不会触发重新渲染的值,或者获取 DOM 元素。

它们的最大区别是什么?

  • useState:存状态,更新会刷新页面
  • useEffect:执行副作用,不是拿来存数据的
  • useRef:存引用或值,但更新不会刷新页面

如果你之前一直觉得这三个 Hook 很绕,那你现在可以直接把它们理解成:

  • useState:页面数据管理员
  • useEffect:页面行为执行器
  • useRef:页面内部小仓库

这样再看 React Hook,很多东西就没那么抽象了。

相关推荐
兆子龙1 小时前
CSS 里的「if」:@media、@supports 与即将到来的 @when/@else
前端
踩着两条虫1 小时前
AI 智能体如何重构开发工作流
前端·人工智能·低代码
代码老中医2 小时前
逃离"Div汤":2026年,当AI写了75%的代码,前端开发者还剩什么?
前端
左夕2 小时前
最基础的类型检测工具——typeof, instanceof
前端·javascript
yuki_uix2 小时前
递归:别再"展开脑补"了,学会"信任"才是关键
前端·javascript
Moment5 小时前
腾讯终于对个人开放了,5 分钟在 QQ 里养一只「真能干活」的 AI 😍😍😍
前端·后端·github
比尔盖茨的大脑5 小时前
AI Agent 架构设计:从 ReAct 到 Multi-Agent 系统
前端·人工智能·全栈
天才熊猫君5 小时前
使用 Vite Mode 实现客户端与管理端的物理隔离
前端