如何防止React中ref值在重渲染时被意外修改?
问题解决:阻止ref对象自动同步初始值
问题场景
开发嵌套复选框列表时,使用ref对象bodyOriginal存储数据库返回的初始值,以此判断内容是否修改并控制保存按钮的激活状态。已确认该ref仅在数据库请求函数中修改且仅执行一次,但点击复选框时出现异常:
- 首次点击功能正常,再次点击时
bodyOriginal的值自动与最新的body同步,导致修改判断逻辑失效 - 直接执行
JSON.stringify(item) !== JSON.stringify(bodyOriginal.current[index])时结果始终为false,bodyOriginal会自动和body同步,尝试useCallback、useMemo均无效
点击复选框的处理函数:
const handleCheckboxGroup = useCallback((event, index, optionIndex) => { const original = JSON.stringify(bodyOriginal.current[index]); setBody(prev => prev.map((item, indexPrev) => { if (indexPrev === index) { item.options[optionIndex].checked = event.target.checked; item.active = JSON.stringify(item) !== original; // 此处判断失效 } return item; }) ); }, []);
核心原因
bodyOriginal存储的是初始数据的内存引用,而非独立的副本。在setBody回调中直接修改prev状态里的item对象时,实际上是在修改和bodyOriginal指向同一内存地址的对象,导致bodyOriginal的值被意外篡改。
解决方案
1. 深拷贝初始数据到ref
获取数据库数据后,对初始数据进行深拷贝,确保bodyOriginal存储的是完全独立的对象,与后续状态中的对象无引用关联:
// 数据库请求示例 const fetchInitialData = async () => { const initialData = await api.get('/your-data'); // 用JSON序列化实现深拷贝(适合无特殊类型的数据) bodyOriginal.current = JSON.parse(JSON.stringify(initialData)); setBody(initialData); };
如果数据包含Date、函数等特殊类型,建议使用专门的深拷贝工具(如lodash的_.cloneDeep)。
2. Immutable 方式更新状态
修改状态时不直接修改原对象,而是创建新的对象层级,保证状态更新是不可变的:
const handleCheckboxGroup = useCallback((event, index, optionIndex) => { const originalItem = bodyOriginal.current[index]; setBody(prev => prev.map((item, indexPrev) => { if (indexPrev !== index) return item; // 创建新的options数组,替换目标选项的checked状态 const newOptions = [...item.options]; newOptions[optionIndex] = { ...newOptions[optionIndex], checked: event.target.checked }; // 创建新的item对象,计算修改状态 const updatedItem = { ...item, options: newOptions, active: JSON.stringify({...item, options: newOptions}) !== JSON.stringify(originalItem) }; return updatedItem; }) ); }, []);
3. 优化修改判断逻辑(可选)
频繁使用JSON.stringify会有性能开销,可在初始化时给每个项保存初始状态快照,或单独跟踪每个项的修改状态,避免重复序列化。
内容的提问来源于stack exchange,提问作者TurtleTail
相关产品推荐
相关产品推荐

