前言
在 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 不会引起渲染?
useRef
和useState
等不同,它的值改变不会引起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 使用 useEffect 、useLayoutEffect 控制副作用 |
--- | 开发者可以控制在 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的变化,在浏览器端进行了渲染。
