React useState初始引用在函数内编辑时被意外修改问题咨询
问题解答
为什么会出现引用被意外篡改的情况
- JavaScript中数组、普通对象都属于引用类型,你从
groups.json导入的数组、数组内的对象、对象下的children数组,本质上都是指向堆内存存储地址的引用。 - 你调用
setFetchedGroups(groups)时,只是把原始导入groups的引用赋值给了状态fetchedGroups,二者指向完全相同的内存数据,并没有生成独立的副本。 splice是会直接修改原数组的变异方法,你直接操作sourceArray[0].children时,修改的就是堆内存中的原始数据,所有指向这个地址的引用(包括原始导入的groups、状态里的fetchedGroups)自然都会同步感知到修改。- 补充说明:React的
useState仅限制你必须通过setFetchedGroups来触发状态更新和组件重渲染,并不会阻止你直接修改引用类型的内部属性,这是JS的语言特性,和React本身无关。
怎么操作才能避免修改原始引用
核心原则是所有修改都基于副本操作,遵循不可变更新规范:
- 初始化状态时就和原始导入值做隔离,存入状态的是原始数据的深拷贝
useEffect(() => { // 用浏览器原生的structuredClone做深拷贝,也可以用JSON.parse(JSON.stringify(groups))、lodash.cloneDeep等方案 setFetchedGroups(structuredClone(groups)) }, [])
- 修改数据时不要直接操作原状态,生成新副本后再通过setter更新状态
// 传入要修改的group索引,不要直接传状态引用 const removeChildren = (targetIndex) => { setFetchedGroups(prevGroups => { // 拷贝顶层数组 const newGroups = [...prevGroups] // 拷贝要修改的group对象 const targetGroup = {...newGroups[targetIndex]} // 生成新的children数组,不用splice改原数组,用filter/slice返回新数组 targetGroup.children = targetGroup.children.filter((_, idx) => idx !== 1) // 替换拷贝后的group到新数组中 newGroups[targetIndex] = targetGroup return newGroups }) } // 调用方式修改为 removeChildren(0)
如果你的数据嵌套层级很深,也可以在修改前先对要操作的对象做一次深拷贝,再修改后替换回状态,避免逐层拷贝的繁琐。
内容的提问来源于stack exchange,提问作者Jeppe Christensen
相关产品推荐
相关产品推荐

