React UI快照工作原理及定时器中state与ref取值差异解析
React中定时器与state/ref的行为差异解析
正如React官方文档所述,React在渲染时会基于当前状态生成UI快照,任何状态变更都会触发新的重新渲染。
这就导致了一个直观的差异:延迟执行的alert如果打印state变量,会显示alert最初被调度时的state值;但如果打印ref变量,会显示alert触发瞬间的ref值。
示例代码
import { useState, useRef } from 'react'; export default function Chat() { const [text, setText] = useState(''); const dummy = useRef(""); dummy.current = text; function handleSend() { setTimeout(() => { alert('Sending: ' + text); }, 5000); } return ( <> <input value={text} onChange={(e) => setText(e.target.value)} /> <button onClick={handleSend}> Send </button> </> ); }
在这段代码中,点击Send按钮后修改输入内容,alert会显示点击Send前的text值——这正是React快照机制的体现。但如果将alert中的text替换为dummy.current,alert则会显示alert触发前输入框的最终值。
一、React是如何调度定时器函数并为其赋值的?
React并没有对定时器函数做特殊调度,定时器完全由浏览器的事件循环处理。核心原因在于闭包对变量的捕获机制:
- 点击Send按钮时,
handleSend被调用,此时创建的setTimeout回调函数会捕获当前渲染周期内的text变量——也就是点击瞬间的state值。 - 后续修改输入框内容会触发state更新,进而触发新的渲染,生成新的
text变量(每个渲染周期的state都是独立的常量)。但之前的定时器回调已经绑定到了旧渲染周期的text上,所以5秒后执行时,依然使用那个旧值。
React的渲染机制让每个渲染周期的变量都形成独立闭包,定时器回调会锁定在创建它时所在的渲染周期变量上,这才是导致现象的根本原因,而非React主动调度定时器。
二、state变量与ref变量的赋值逻辑有何不同?
state变量的逻辑
- state由React管理,每次调用
setText更新state时,React会触发新渲染。在新渲染周期中,useState返回更新后的state值——这个值是当前渲染周期的常量,每个渲染周期的state都是独立的,后续的state变更只会生成新的快照,不会修改当前渲染周期内的state变量。 - 简言之,state是“快照式”的:每个渲染周期拿到的state都是当时状态的固定快照,渲染完成后这个值就不会被后续操作改变。
ref变量的逻辑
- ref是
useRef创建的普通JavaScript对象,它的current属性可以直接修改,且修改不会触发组件重新渲染。 - ref的
current是可变引用:所有渲染周期共享同一个ref对象,无论组件重新渲染多少次,dummy始终指向同一个对象。每次渲染时执行dummy.current = text,都是在更新同一个对象的属性值。定时器回调访问dummy.current时,会直接读取该对象的最新属性值,也就是alert触发瞬间的最新输入内容。
内容的提问来源于stack exchange,提问作者Indra
相关产品推荐
相关产品推荐

