🔥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的变化,在浏览器端进行了渲染。

相关推荐
掘金安东尼6 分钟前
解读 hidden=until-found 属性
前端·javascript·面试
1024小神15 分钟前
jsPDF 不同屏幕尺寸 生成的pdf不一致,怎么解决
前端·javascript
滕本尊15 分钟前
构建可扩展的 DSL 驱动前端框架:从 CRUD 到领域模型的跃迁
前端·全栈
借月16 分钟前
高德地图绘制工具全解析:线路、矩形、圆形、多边形绘制与编辑指南 🗺️✏️
前端·vue.js
li理16 分钟前
NavPathStack 是鸿蒙 Navigation 路由的核心控制器
前端
二闹19 分钟前
一招帮你记住上次读到哪儿了?
前端
li理23 分钟前
核心概念:Navigation路由生命周期是什么
前端
古夕25 分钟前
my-first-ai-web_问题记录02:Next.js 15 动态路由参数处理
前端·javascript·react.js
梦里寻码25 分钟前
自行食用 uniapp 多端 手写签名组件
前端·uni-app
前端小白199528 分钟前
面试取经:工程化篇-webpack性能优化之热替换
前端·面试·前端工程化