useRef包裹的函数内访问React state不更新如何解决
问题产生原因
- 核心原因是陈旧闭包+useRef的初始化特性:
useRef仅会在组件首次渲染时执行传入的初始化逻辑,后续组件重渲染时,ref.current保存的值不会自动更新。你首次渲染时传给useRef的回调函数,闭包捕获的是首次渲染阶段的getCount方法,以及当时的初始state值count=0,后续重渲染时onClick.current始终指向第一次创建的旧函数,自然读不到更新后的state。 - 你写的
.bind(App)是完全无效的操作:函数组件不存在类组件那样的组件实例,把函数的this绑定到App组件函数本身,既访问不到组件内部的state,也不会自动同步最新值,对解决问题没有任何帮助。 - React函数组件的渲染机制决定了:函数组件每次重渲染都会从头执行一遍整个函数体,每一轮渲染都有独立的state、变量、方法快照,如果你把某一轮渲染生成的函数存在ref里不更新,它永远只能访问对应轮次的快照值。
- 额外的隐藏问题:你直接把
onClick.current绑定给DOM的onClick事件,绑定动作发生在渲染阶段,拿到的也是当次渲染时的current值,就算后续更新current,已经绑定的事件处理器还是旧的。
修复方案
根据实际场景选择对应方案即可:
方案1:用ref同步最新state,适配需要固定回调引用的场景
如果场景要求事件处理器引用保持稳定(比如要传给做了memo优化的子组件避免重渲染),可以额外建一个ref存储最新的state值,每次渲染后把最新state同步到ref上,回调里直接读ref的最新值即可:
import React, { useState, useRef, useEffect } from 'react' import ReactDOM from 'react-dom' const App = function () { const [count, setCount] = useState(0) // 存储最新state的ref const latestCountRef = useRef(count) // 每次count变化后同步最新值到ref useEffect(() => { latestCountRef.current = count }, [count]) console.log('render时count值:', count) // 回调初始化一次即可,永远读ref上的最新值 const onClick = useRef(() => { console.log('事件内拿到的count:', latestCountRef.current) }) return ( <> <div onClick={() => setCount(prev => prev + 1)}> increment count </div> {/* 包一层箭头函数,触发时才读取最新的current回调 */} <div onClick={() => onClick.current()}> {count} </div> </> ) } const wrapper = document.getElementById('root') ReactDOM.render(<App />, wrapper)
方案2:每次渲染更新ref上的回调
同样适用于需要保持ref引用稳定的场景,每次重渲染后把捕获了最新state的回调赋值给ref.current,触发事件时调用最新的回调即可:
import React, { useState, useRef, useEffect } from 'react' import ReactDOM from 'react-dom' const App = function () { const [count, setCount] = useState(0) const getCount = () => count console.log('render时count值:', getCount()) const onClick = useRef(() => {}) // 每次渲染后更新ref上的回调为最新版本 useEffect(() => { onClick.current = () => { console.log('事件内拿到的count:', getCount()) } }) return ( <> <div onClick={() => setCount(prev => prev + 1)}> increment count </div> <div onClick={() => onClick.current()}> {count} </div> </> ) } const wrapper = document.getElementById('root') ReactDOM.render(<App />, wrapper)
方案3:无特殊性能需求直接用普通函数
如果没有强制要求回调引用保持稳定,完全没必要用useRef包裹事件回调,直接定义普通函数即可,React每次重渲染会自动更新DOM上绑定的事件处理器,天然能拿到最新的state:
import React, { useState } from 'react' import ReactDOM from 'react-dom' const App = function () { const [count, setCount] = useState(0) const getCount = () => count console.log('render时count值:', getCount()) // 普通回调,每次渲染生成最新版本 const onClick = () => { console.log('事件内拿到的count:', getCount()) } return ( <> <div onClick={() => setCount(prev => prev + 1)}> increment count </div> <div onClick={onClick}> {count} </div> </> ) } const wrapper = document.getElementById('root') ReactDOM.render(<App />, wrapper)
注意:更新state时推荐写成
setCount(prev => prev + 1)的形式,避免闭包导致的state更新不及时问题。
内容的提问来源于stack exchange,提问作者jsindos
相关产品推荐
相关产品推荐

