踩坑实录:把 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 的理解从「表面记忆」变成了「深层理解」。希望我的学习过程能帮到更多新手小伙伴~

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

相关推荐
有意义2 小时前
现代 React 路由实践指南
前端·vue.js·react.js
三木檾2 小时前
Cookie 原理详解:Domain / Path / SameSite 一步错,生产环境直接翻车
前端·浏览器
二DUAN帝2 小时前
像素流与UE通信
前端·javascript·css·ue5·html·ue4·html5
1024小神2 小时前
cloudflare+hono框架实现jwtToken认证,并从token中拿到认证信息
前端
jinmo_C++2 小时前
从零开始学前端 · HTML 基础篇(二):常用文本标签与排版基础
前端·html
2501_944711432 小时前
A2UI : 以动态 UI 代替 LLM 文本输出的方案
开发语言·前端·ui
生活在一步步变好i2 小时前
前端加载优化核心知识点详解
前端
C_心欲无痕2 小时前
理解前端的运行时与编译时
前端
3824278272 小时前
JS正则表达式实战:核心语法解析
开发语言·前端·javascript·python·html