踩坑实录:把 useRef 写进 JSX 后,我终于分清它和 useState 的核心差异

作为 React 新手,刚接触 Hooks 时,很容易混淆 useStateuseRef------两者都能存储数据,那到底该怎么选?

我最近通过一个「点击计数」的简单练习,结合一个关键疑问,彻底理清了它们的核心区别。这篇文章就把我的学习过程和理解分享出来,希望能帮到同样困惑的小伙伴。

一、先从一个极简练习示例入手

我最初的需求很简单:做一个点击计数组件,需要满足两个功能:

  1. 显示「当前轮次点击数」,支持重置;
  2. 显示「页面加载后的总点击数」,重置当前轮次后总次数不清零。

结合 useStateuseRef,最终写出了这样的代码(可直接运行):

ini 复制代码
import { useState, useRef } from 'react';

function ClickCounter() {
  // useState:存储当前轮次点击数(驱动视图更新)
  const [currentCount, setCurrentCount] = useState(0);
  
  // useRef:存储总点击数(持久化保存,修改不触发重渲染)
  const totalCountRef = useRef(0);

  // 点击计数:同时更新两个数据
  const handleClick = () => {
    // 更新当前轮次计数(触发重渲染)
    setCurrentCount(prev => prev + 1);
    
    // 更新总计数(直接修改 current,不触发重渲染)
    totalCountRef.current += 1;
    
    // 控制台打印总计数,验证数据
    console.log('总点击数(ref):', totalCountRef.current);
  };

  // 重置当前轮次计数(总计数不变)
  const handleResetCurrent = () => {
    setCurrentCount(0);
  };

  return (
    <div style={点击计数练习当前轮次点击数:{currentCount}总点击数(持久化):{totalCountRef.current}<button onClick={ }}>
        点击计数
      <button onClick={handleResetCurrent}>
        重置当前计数
      
  );
}

export default function App() {
  return <ClickCounter />;
}

二、我的核心疑问:useRef 不是不能驱动视图吗?为什么能写在 JSX 里?

写完代码后,我立刻产生了一个疑问:

之前学习时知道 useRef 用于存储不需要驱动视图更新的数据,修改 ref.current 不会触发重渲染。但这个示例里,我把 totalCountRef.current 直接写在了 JSX 里,这难道不矛盾吗?这样写到底有没有问题?

这个疑问也让我意识到,很多新手对 useStateuseRef 的理解,可能都停留在「表面定义」,没有深入到「渲染逻辑」层面。

三、彻底搞懂:useRef 写在 JSX 里到底行不行?

带着疑问,我梳理了核心结论,再通过代码执行过程验证,终于彻底明白:

1. 语法上:完全没问题

React 的 JSX 支持嵌入任意合法的 JavaScript 表达式,totalCountRef.current 本质就是一个普通的 JS 变量(或数值、字符串等),所以把它写在 JSX 里不会报错,属于合法写法。

2. 功能上:能显示但不能「主动驱动更新」

这是最关键的一点------useRef 的值可以显示在页面上,但修改它不会触发组件重渲染,因此页面上的显示值不会「实时更新」,只会显示组件上一次渲染时的旧值。

我们结合示例代码的执行过程,一步步拆解:

步骤 1:组件首次渲染

  • currentCount = 0useState 初始值);
  • totalCountRef.current = 0useRef 初始值);
  • JSX 渲染结果:当前轮次点击数:0,总点击数:0。

步骤 2:第一次点击「计数」按钮

  • 执行 setCurrentCount(prev => prev + 1):修改 useState 状态,触发组件重渲染
  • 执行 totalCountRef.current += 1totalCountRef.current 变成 1,但这个修改不会触发重渲染;
  • 因为 useState 触发了重渲染,JSX 会重新读取所有值,此时总点击数显示为 1(这里能更新是因为 useState 带动了渲染,不是 useRef 自己驱动的)。

步骤 3:验证「只改 useRef 不触发更新」

为了更直观验证,我加了一个「只修改总计数」的按钮:

xml 复制代码
<button onClick={ totalCountRef.current += 1}>只改总计数

点击这个按钮后:

  • 控制台打印 totalCountRef.current,会发现数值确实增加了;
  • 但页面上的总点击数没有任何变化------因为没有修改 useState 状态,组件没有重渲染,JSX 里的 totalCountRef.current 还是上一次渲染的旧值。

四、useState 与 useRef 的核心差异总结

通过这个示例和疑问,我终于理清了两者的核心区别,用表格总结最清晰:

对比维度 useState useRef
核心作用 存储驱动视图更新的状态 存储持久化、不驱动视图的数据
修改后是否触发重渲染 是(调用 setXxx 必触发) 否(直接改 current 不触发)
数据更新方式 不可变更新(必须用 setXxx) 可变更新(直接改 current)
写在 JSX 里的效果 实时更新显示 不主动更新,需依赖其他逻辑触发渲染
适用场景 页面需要实时显示的数据(计数、表单值、列表等) 定时器 ID、DOM 元素引用、持久化不显示的状态(如总点击数)

五、新手避坑指南

梳理完这些内容后,我总结了几个新手容易踩的坑,供大家参考:

1. 不要用 useRef 存储需要实时显示的数据

如果数据需要在页面上实时更新(比如当前轮次点击数),一定要用 useState。用 useRef 只会导致页面显示滞后。

2. 不要误以为 useRef 不能写在 JSX 里

语法上完全可以写,但要清楚它的局限性------不能主动驱动更新。只有数据不常变化(如固定 ID、初始化后的常量)时,写在 JSX 里才合理。

3. 避免滥用「强制更新」让 useRef 实时显示

如果非要让 useRef 的值实时显示,有人会用「空 state」强制触发渲染:

ini 复制代码
const [, forceUpdate] = useState({});
const handleClick = () => {
  totalCountRef.current += 1;
  forceUpdate({}); // 强制触发渲染
};

这种写法虽然能实现效果,但不推荐------既然需要实时更新,直接用 useState 管理数据更符合 React 设计理念,代码也更简洁。

4. 普通变量无法替代 useRef 做持久化

新手可能会想:"既然 useRef 只是持久化数据,用普通变量不行吗?" 答案是不行------组件每次重渲染时,函数内部的普通变量都会重新初始化(比如 let totalCount = 0 会每次重置为 0),而useRefcurrent 在组件整个生命周期内都会保留数据。

六、总结

其实 useStateuseRef 的选择逻辑很简单:

如果数据需要驱动视图更新 → 用 useState;

如果数据只需要持久保存、不需要驱动视图 → 用 useRef。

这次通过一个简单的练习示例,加上自己的疑问和梳理,让我对这两个 Hooks 的理解从「表面记忆」变成了「深层理解」。希望我的学习过程能帮到更多新手小伙伴~

如果有疑问或不同看法,欢迎在评论区交流~

相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax