为何React状态修改会改变原数组?如何避免默认数组被篡改
嘿,这个问题其实是JavaScript里引用类型的经典坑,我来给你掰扯清楚~
为什么const定义的数组会被修改?
首先得搞懂:const关键字只是保证变量本身的引用不会被改变——也就是说你不能给defaultButtons重新赋值成另一个数组,但它指向的数组(属于引用类型)内部的内容是完全可以修改的。
当你把defaultButtons直接赋值给state.buttons时,你并没有创建新数组,只是把同一个数组的引用传给了组件状态。这就像两个人共用同一个笔记本,不管谁在上面改内容,另一个人看到的都会是修改后的版本,哪怕你用const“锁”住了笔记本的名字,也拦不住里面的内容被改。
怎么避免修改原数组?
核心思路就是:给组件状态传一个原数组的副本,而不是原数组本身。这样状态和原数组就指向两个独立的内存空间,修改状态里的数组就不会影响到原数组了。
方法1:扩展运算符创建浅拷贝
这是最常用的方式,简单直观:
const defaultButtons = [{on: false},{on: false},{on: false}]; class Keyboard extends Component { // 创建原数组的浅拷贝作为初始状态 state = { buttons: [...defaultButtons] } componentDidMount(){ // 修改状态时直接替换为新数组,不会影响原数组 this.setState({buttons: [{on: true},{on: true},{on: true}]}) console.log(defaultButtons) // 现在会输出 [{on: false},{on: false},{on: false}] 啦! } render(){ return(<div />) } }
方法2:Array.from创建浅拷贝
效果和扩展运算符一致,写法不同而已:
state = { buttons: Array.from(defaultButtons) }
进阶:需要深拷贝的场景
上面的方法都是浅拷贝——如果数组里的元素是对象,拷贝后的数组里的对象还是和原数组里的对象共享同一个引用。如果你的场景需要修改对象内部属性,同时不想影响原数组里的对象,那就要用深拷贝:
比如用JSON.parse(JSON.stringify())(适合没有函数、Symbol等特殊类型的简单场景):
state = { buttons: JSON.parse(JSON.stringify(defaultButtons)) }
你也可以手写深拷贝函数,或者用第三方工具(比如Lodash的_.cloneDeep),不过普通场景下JSON方法足够用了。
修改状态的正确姿势
另外,在修改状态里的数组元素时,也要遵循React的不可变性原则,不要直接修改原状态数组,而是创建新的数组和对象:
// 示例:修改第一个按钮的on状态 this.setState(prevState => { // 先拷贝整个数组 const newButtons = [...prevState.buttons]; // 再拷贝要修改的对象,更新目标属性 newButtons[0] = { ...newButtons[0], on: true }; return { buttons: newButtons }; });
这样操作就完全不会影响到最初的defaultButtons数组啦~
内容的提问来源于stack exchange,提问作者jeyko

