一、前言本文从三条核心规则切入串联原理同时区分普通 let 变量、useState、useRef 在 StrictMode 双渲染下的差异化表现。二、三大核心基础规则1、useRef 底层运行规则组件每一次重渲染, const ref useRef(null) 代码都会执行但组件生命周期内仅首次挂载时创建唯一 ref 对象后续所有渲染全部复用同一个 ref 实例。2.useEffect运行规则渲染到屏幕之后执行 setup下次执行 (即setup) 前先清理上一次 (即cleanup) 组件卸载时最后清理一次。当使用 useEffect 时effect函数包括cleanup会通过闭包(基础类型存值复杂类型存地址)捕获它(即effect)被创建时所在渲染周期的变量值/地址。3.React StrictMode 双渲染规则区分 let/useState/useRef开启 StrictMode 后开发环境组件渲染逻辑会执行两次目的是模拟并发渲染中组件卸载再重建场景提前暴露渲染阶段的副作用脏代码。(拓展) 三类变量在渲染阶段的本质区别let每次组件渲染函数重新执行都会在栈内存生成全新独立变量两次渲染的 let 变量互不影响不存在跨渲染污染数据问题因此双渲染下修改不会触发Eslint报错。useRef数据保存在 React 内部 fiber 节点不会在每次渲染时重新创建。渲染阶段修改 current 第二次渲染拿到的是被第一次修改后的值因此双渲染下修改触发Eslint报错。useState数据存放在 React 内部 fiber 节点跨渲染共享状态渲染阶段直接调用 setState 更新会推入更新队列触发新一轮渲染两次连续执行极易造成无限循环渲染React 调度系统捕获后直接抛出运行警告。三、三者完整关联逻辑function Demo(){ const inst () {} const ref useRef(inst) useEffect((){ return ()ref.current() },[]) return (div/div) }上面的代码中cleanup 函数直接使用了 ref.current 。这存在问题ref.current 是可变的从 cleanup 函数创建到真正执行之间ref.current 可能已经被其他代码修改了也就是 cleanup 里的 ref.current 被修改了。StrictMode 下的双渲染机制会导致以下时序setup1执行ref.current 实例Acleanup1执行此时 ref.current 仍是实例A setup2执行ref.current实例B被修改真正卸载时 cleanup2执行此时 ref.current 已经是实例B正确做法是把 ref.current 的当前值缓存到局部变量function Demo(){ const inst () {} const ref useRef(inst) useEffect((){ const inst ref.current return ()inst() },[]) return (div/div) }四、总结useRef 单例特性提供安全基础StrictMode 双渲染提供编码约束二者共同催生了 useEffect 缓存的 React 官方标准写法。
React 从 StrictMode 双渲染入手:深入理解 useRef、useEffect 编码规范由来
发布时间:2026/6/4 22:05:35
一、前言本文从三条核心规则切入串联原理同时区分普通 let 变量、useState、useRef 在 StrictMode 双渲染下的差异化表现。二、三大核心基础规则1、useRef 底层运行规则组件每一次重渲染, const ref useRef(null) 代码都会执行但组件生命周期内仅首次挂载时创建唯一 ref 对象后续所有渲染全部复用同一个 ref 实例。2.useEffect运行规则渲染到屏幕之后执行 setup下次执行 (即setup) 前先清理上一次 (即cleanup) 组件卸载时最后清理一次。当使用 useEffect 时effect函数包括cleanup会通过闭包(基础类型存值复杂类型存地址)捕获它(即effect)被创建时所在渲染周期的变量值/地址。3.React StrictMode 双渲染规则区分 let/useState/useRef开启 StrictMode 后开发环境组件渲染逻辑会执行两次目的是模拟并发渲染中组件卸载再重建场景提前暴露渲染阶段的副作用脏代码。(拓展) 三类变量在渲染阶段的本质区别let每次组件渲染函数重新执行都会在栈内存生成全新独立变量两次渲染的 let 变量互不影响不存在跨渲染污染数据问题因此双渲染下修改不会触发Eslint报错。useRef数据保存在 React 内部 fiber 节点不会在每次渲染时重新创建。渲染阶段修改 current 第二次渲染拿到的是被第一次修改后的值因此双渲染下修改触发Eslint报错。useState数据存放在 React 内部 fiber 节点跨渲染共享状态渲染阶段直接调用 setState 更新会推入更新队列触发新一轮渲染两次连续执行极易造成无限循环渲染React 调度系统捕获后直接抛出运行警告。三、三者完整关联逻辑function Demo(){ const inst () {} const ref useRef(inst) useEffect((){ return ()ref.current() },[]) return (div/div) }上面的代码中cleanup 函数直接使用了 ref.current 。这存在问题ref.current 是可变的从 cleanup 函数创建到真正执行之间ref.current 可能已经被其他代码修改了也就是 cleanup 里的 ref.current 被修改了。StrictMode 下的双渲染机制会导致以下时序setup1执行ref.current 实例Acleanup1执行此时 ref.current 仍是实例A setup2执行ref.current实例B被修改真正卸载时 cleanup2执行此时 ref.current 已经是实例B正确做法是把 ref.current 的当前值缓存到局部变量function Demo(){ const inst () {} const ref useRef(inst) useEffect((){ const inst ref.current return ()inst() },[]) return (div/div) }四、总结useRef 单例特性提供安全基础StrictMode 双渲染提供编码约束二者共同催生了 useEffect 缓存的 React 官方标准写法。