React 18+特定场景下将ref.current作为useEffect依赖是否安全?
myRef.current作为Effect依赖是否存在实际风险? 前提
- 我知道通过
const myref = useRef()定义的ref本身更新不会触发组件重渲染 - 我也清楚有一条ESLint规则建议不要把
myRef.current作为Effect依赖,也不要省略Effect里的依赖 - 我了解可以用ref回调,但我用的ref是在Hook内部,完全不依赖DOM
- 我考虑过用多个Effect,但我的场景里会出现竞态条件——它们都操作同一个状态,一个写入新状态,另一个读取的却是旧的未更新状态
用例说明
我用myRef来跟踪状态,不直接把state作为依赖,以此避免上述竞态条件,同时让外部调用setState时能自动触发这个Hook。
这个Hook的设计保证了:当value这类状态变化,或者外部调用setState(不是ref自身更新)时,Hook会重新运行。我已经在React v18里验证过,ref变化时第一个Effect会重新运行,目前没发现明显问题。
const [ state, setState ] = useState({}) const stateRef = useRef(state) const [ value, setValue ] = useState(false) const myHook = () => { useEffect(() => { if (!value) { // 计算并存储状态:setState(computedState),随后setValue(true) } else { // 在单独的渲染周期(即`value`变为true时),从stateRef.current读取状态(通过第二个Effect与`state`同步),并进行其他操作。此时(Effect第二次运行时)state尚未更新 } }, [ value, stateRef.current] ) useEffect(() => { stateRef.current = state // 修正原代码笔误:useRef → stateRef }, [state]) return [state, setState] }
问题解答
把stateRef.current作为Effect依赖确实存在实际风险,核心原因如下:
依赖追踪的不可靠性:React的Effect依赖对比是在渲染阶段执行的,而
ref.current可以在渲染之外的任意时机(比如Effect、事件回调)被修改。这意味着React无法精准追踪它的变化——即便你修改了stateRef.current,渲染阶段可能仍拿着旧值做对比,导致Effect不按预期触发。版本兼容性隐患:你在React v18里测试正常,但这属于React的未定义行为。官方从未承诺过修改
ref.current会触发Effect重新执行,未来版本的React可能会优化掉这种“意外触发”的逻辑,到时你的代码会直接失效。代码可维护性差:依赖
ref.current会让逻辑变得晦涩难懂,后续调试或接手的开发者很难理解依赖的合理性,一旦出现竞态或触发异常,排查成本会非常高。
替代方案
既然你的核心需求是避免竞态、同时能拿到最新state,可以调整逻辑,把状态同步和后续操作合并到同一Effect中,或者利用Effect的依赖特性直接使用state:
const [ state, setState ] = useState({}) const [ value, setValue ] = useState(false) const myHook = () => { // 同步state并执行后续操作 useEffect(() => { if (value) { // 这里直接用state即可,它是Effect的依赖,必然是最新值 // 执行你需要的操作 } }, [state, value]) useEffect(() => { if (!value) { const computedState = /* 你的计算逻辑 */ setState(computedState) setValue(true) } }, [value]) return [state, setState] }
这种写法既规避了依赖ref.current的风险,又能保证逻辑的顺序性,不会出现读取旧状态的竞态问题。
内容的提问来源于stack exchange,提问作者dragonmnl

