React中如何实现并发安全且取值正确的useLatest Hook?
支持并发安全且始终返回正确值的
useLatest 实现方案 首先明确两种常见实现的核心缺陷:
- 渲染阶段直接更新ref的方案:React 18 并发模式下,可中断、可回退的渲染过程会直接修改全局共享的ref对象,未提交的渲染更新会污染ref状态,导致并发场景下出现非预期的取值错误。
- 依赖
useEffect更新ref的方案:useEffect在渲染提交完成后才执行,渲染阶段、提交前的同步逻辑都只能拿到上一次提交的旧值,不满足渲染期间取最新值的需求。
你自己尝试的实现思路是合理的,通过区分临时渲染值和已提交值解决了并发污染的问题,但存在的两个缺陷核心根因是每次渲染返回新的ref对象:旧闭包捕获的旧ref对象里的startingUpdateCount是旧渲染批次的值,自然会读取到旧的已提交值。
优化后的稳定实现
import { useRef, useLayoutEffect } from 'react'; function useLatest<T>(value: T): { readonly current: T } { const innerRef = useRef({ // 存储已提交到视图的正式值 committedValue: value, // 存储当前渲染批次的最新临时值 latestValue: value, }); // 渲染阶段先更新当前批次的临时最新值 innerRef.current.latestValue = value; useLayoutEffect(() => { // 只有渲染提交成功后,才更新正式的已提交值 innerRef.current.committedValue = value; }, [value]); // 用useRef保证返回的代理对象永远稳定,不会随渲染变化 const proxyRef = useRef({ get current() { // 任意场景下都优先取最新的渲染值,渲染阶段取当前批次的值,提交后latestValue和committedValue自动对齐 return innerRef.current.latestValue; } }).current; return proxyRef; }
如果需要支持手动修改current值,可以调整为兼容MutableRefObject的版本:
import { useRef, useLayoutEffect, MutableRefObject } from 'react'; function useLatest<T>(value: T): MutableRefObject<T> { const innerRef = useRef({ committedValue: value, latestValue: value, }); innerRef.current.latestValue = value; useLayoutEffect(() => { innerRef.current.committedValue = value; }, [value]); const proxyRef = useRef({ get current() { return innerRef.current.latestValue; }, set current(newVal: T) { innerRef.current.latestValue = newVal; } }).current; return proxyRef; }
方案验证
- 并发安全:只有渲染提交后的
useLayoutEffect才会更新已提交值,未提交的渲染哪怕临时修改了latestValue,要么后续重新渲染会用对应批次的value覆盖,要么不会有外部逻辑在未提交阶段读取(React保证事件、副作用都只会在渲染提交后执行),不会出现ref被未提交更新污染的问题。 - 取值永远正确:
- 渲染阶段读取:直接拿到当前渲染批次传入的最新value,不会出现旧值问题
- 提交后事件、副作用中读取:拿到的是最新提交的value,和视图状态完全对齐
- 闭包兼容:返回的代理对象永远稳定,旧闭包捕获该对象后,后续读取永远会拿到最新的value,解决了你提到的
function1读取旧值的问题。
内容的提问来源于stack exchange,提问作者Leo Jiang
相关产品推荐
相关产品推荐

