🔥React 新手必看!useRef 竟然不能触发 onChange?原来是这个原因!

前言

在 React 的函数组件中,随着 Hooks 的引入,我们拥有了更多灵活的方式来处理状态和副作用。然而,在某些场景下,我们仍然需要像类组件中那样进行"命令式"的操作,例如直接访问 DOM 元素, 这时,useRef 这个 Hook 就显得尤为重要。

useRef 提供了一个稳定的引用,用于在不触发重新渲染的情况下保留组件的状态;

本文将深入探讨这个 Hook 的作用、使用场景以及最佳实践,帮助你在实际项目中更加得心应手地使用它。

useRef

useRef 是什么?有什么用?

useRef 是 React Hooks 中的一个重要 API,主要用于在函数组件中持久化保存可变值,或直接访问 DOM 节点。

  • 作用 :创建一个可变的引用对象(ref),其 .current 属性可存储任意值,且在组件生命周期内保持不变(不会因重新渲染而重置)。
  • 返回值{ current: initialValue },初始值为传入的参数 initialValue(默认为 null)。

基本用useRef来:

  • 直接操作DOM元素
  • 储存一个可变值(可用来调试)

之后我们可以用 inputRef.current来访问绑定的值,也能对其做出一些修改。

看下面这个绑定DOM的例子

jsx 复制代码
import { useRef } from 'react';

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

  const focusInput = () => {
    inputRef.current.focus(); // 操作DOM
  };

  return (
    <div>
      <input ref={inputRef} type="text" />  {/* ref={inputRef},在DOM元素中绑定引用*/}
      <button onClick={focusInput}>Focus</button> {/* 点一下就会聚焦到input框 */}
    </div>
  );
}

它还可以储存一个可变值:

jsx 复制代码
import { useReducer, useState, useRef, useEffect } from 'react';

function Goods() {
    const [count, setCount] = useState(0);
    const countRef = useRef(0);
    useEffect(() => {
        countRef.current = count;
        // 这一步是同步的,不是异步的,当setCount异步更新时,这一句已经执行完毕
    }, [count])

    return (
        <div>
            <p>当前count值:{count}</p>
            <p>当前countRef值:{countRef.current}</p>
            <button onClick={() => setCount(count + 1)}>增加</button>
        </div>
    );
}
export default Goods;

我们可以看到,我们能够直接用useRef的值,一般配合useEffect使用,展现数据状态的前一个状态啊,让我们更好理解这个App是怎么运行的,类似于调试功能一样。

useRef 不会引起渲染?

useRefuseState等不同,它的值改变不会引起react的渲染,因为它的值改变后,react并不知晓它的变化

  • 如果你用 useState 管理输入框的值(比如 value={text}),React 会保持状态和 DOM 同步。
  • 但如果你绕过状态,直接改 DOM 的 value,React 不会知道这个变化,所以不会触发重新渲染。
  • 这种方式是"非受控操作"。

但是这里我们运用了useEffect完成了和页面的同步渲染。

看到下面这个例子:

jsx 复制代码
function TextInput() {
  const inputRef = useRef(null);

  const changeValue = () => {
    inputRef.current.value = 'Hello from useRef!';
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={changeValue}>Change Value</button>
    </div>
  );
}

在这里我们点击一下按钮,输入框的值就变成了'Hello from useRef!',这时候就有人要问了:"哎!主播你不是说useRef不会触发渲染吗?这里的值确实改变了啊?"

这里我们是直接操作的DOM,DOM的值改变了,浏览器直接进行了渲染,我们所说的"不渲染",是指的React不会检测到这里,不会进行渲染。

React 管理的是虚拟 DOM 和状态之间的同步。我们通过 useRef 改的是真实 DOM,React 蒙在鼓里,所以不会重新渲染。但真实 DOM 变了,浏览器当然会显示出来。

接下来看一个例子,看看你是不是明白了:

jsx 复制代码
function TextInput() {
  const [text, setText] = useState('');
  const inputRef = useRef(null);

  const changeValue = () => {
    inputRef.current.value = 'Hello from useRef!';
  };

  return (
    <div>
      <input
        ref={inputRef}
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button onClick={changeValue}>Change Value</button>
    </div>
  );
}

提问:当我先输入Good day!,再点击button后,我的text此时是什么?

答案是:Good day!

因为输入框并没有监听到任何输入事件,onChange没有监听到,我们直接修改了输入框的值,直接操作了DOM,但不属于用户输入事件,没有触发React监听数据状态改变的机制 ,所以text仍然是我们之前的输入值。

浏览器渲染 & React渲染的协作

这是 React 开发中的一个核心问题,为什么一会讲 React 的渲染 一会讲 浏览器的渲染 ?接下来给你解惑~

结论

React 的"渲染"是虚拟的、逻辑层面的;浏览器的"渲染"是真实的、物理显示在页面上的。

React 用虚拟 DOM 更新真实 DOM,再由浏览器将这些真实 DOM 的变化渲染到屏幕上。

React 的渲染(逻辑渲染)

在 React 中,"渲染"通常指的是:

组件函数的执行

  • 当组件的状态(state)或 props 改变时,组件函数会重新执行。
  • 这个过程叫做"re-render"(重新渲染)。
  • 它生成的是一个虚拟 DOM(React element tree)。
jsx 复制代码
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
  • 点击按钮后,setCount 会触发 App 组件重新执行(re-render)。
  • React 会对比新旧虚拟 DOM,找出需要更新的真实 DOM 节点(Diffing 算法)。

React DOM 更新(Commit 阶段)

  • React 会把这些更新反映到真实 DOM 上(例如修改文本、属性、增删节点等)。
  • 这一步是 React 控制的,发生在浏览器主线程中。

浏览器的渲染(物理渲染)

当你操作了 DOM(无论是 React 还是手动操作),浏览器需要将这些变化显示到页面上。

这个过程叫 浏览器渲染(Paint + Layout),一般就是感知到DOM变化后,在浏览器上回流重绘,重新画像素点。

这整个过程,React 并不管,它只负责更新 DOM。 是浏览器自动感知 DOM 的变化,并决定什么时候渲染到屏幕上。

React 和浏览器如何配合?

React 的行为 浏览器的行为 协作过程
状态更新触发 re-render --- React 检测状态变化,重新生成虚拟 DOM
React 比较虚拟 DOM,更新真实 DOM --- React 把变化反映到真实 DOM
--- DOM 变化触发浏览器的 Layout 和 Paint 浏览器感知到 DOM 变化,重新渲染页面
React 使用 useEffectuseLayoutEffect 控制副作用 --- 开发者可以控制在 DOM 更新前/后执行代码

常见误解

误解 正确理解
"React 渲染就是页面上显示的内容变了" React 的渲染是组件函数执行,不一定导致真实 DOM 变化
"React 控制浏览器的渲染" React 控制 DOM 更新,浏览器决定什么时候绘制
"每次 setState 都会触发重新渲染" React 会优化,比如批处理更新、跳过不必要的渲染

小小的总结

React 负责更新虚拟 DOM 和真实 DOM,浏览器负责将这些 DOM 的变化真正渲染到屏幕上。两者协作:React 更新逻辑结构,浏览器负责显示。

结语

这一期我们了解了useRef的作用,它可以用来直接操作DOM,也可以用来记录上一次数据变化时的状态,是个很好的调试工具,帮你理解APP的工具,之后我们由useRef引出了react的渲染机制,为什么onChange事件下useRef没有触发react的渲染呢?

这是因为useRef直接跳过了react去操作了真实的DOM,react 并不知晓它的变化,同时这也不属于用户的输入事件,导致onChange没有监听到,但是浏览器检测到了真实DOM的变化。所以在这里只有浏览器进行了渲染,react则没有。

react的渲染是逻辑渲染 ,它监听数据的变化,数据一旦变化就会重新渲染组件,也就是重新执行组件并生成虚拟DOM和真实DOM相比较,然后自己操作DOM,最后浏览器检测到了真实DOM的变化,在浏览器端进行了渲染。

相关推荐
小爱同学_8 小时前
一次面试让我重新认识了 Cursor
前端·面试·程序员
golang学习记8 小时前
AI 乱写代码?不是模型不行,而是你的 VS Code 缺了 Context!MCP 才是破局关键
前端
星光不问赶路人8 小时前
Vite 中的 import.meta.glob vs 动态导入:该用哪个?
前端·vite
疯狂踩坑人8 小时前
【万字长文】让面试没有难撕的JS基础题
javascript·面试
z_y_j2299704389 小时前
服务器中使用Docker部署前端项目
服务器·前端·docker·容器
迪丽热爱9 小时前
解决【npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。】问题
前端·npm·node.js
数字冰雹9 小时前
图观 流渲染场景服务器
服务器·前端·数据库·数据可视化
李明卫杭州9 小时前
详细讲解js中的ResizeObserver
前端·javascript
千叶寻-10 小时前
package.json详解
前端·vue.js·react.js·webpack·前端框架·node.js·json
召摇10 小时前
Redis与PostgreSQL缓存性能终极对决:7千次/秒真的够用吗?
redis·postgresql·面试