状态机state如果是对象数组,就不能进行浅拷贝吗?



  • 今天在开发的时候发现的一个奇怪的现在,如果状态机是一个数组,数组内是对象Object的集合,尝试了各种方法都不能进行浅拷贝。
    简单的用一个按钮执行点击函数进行测试。

    /*构造函数中设置两个状态机变量,一个是数组一个是数字*/
    constructor(props){
        super(props);
        this.state={
              addressData:[{key:1,contact: 1, address: "测试地址1",isDefault:3},],
              abc:0
        }
    }
    
    /*随便写个按钮button,这里通过一个简单的点击函数改变变量的值*/
    onClickDefaultAddress(){
        let temp2= [...this.state.addressData]; /*数组解构赋值,百度说这样可以浅拷贝*/
        temp2[0].address="临时变量改变地址";
    
        let temp_abc = this.state.abc;
        temp_abc++;
    
        /*打印输出*/
        console.log(temp_abc, this.state.abc);
        console.log(temp2[0].address, this.state.addressData[0].address);
    
      }
    

    然后打印输出,结果是下面这样的:0_1539244821103_a3701878-de9e-4a76-8d25-69fd20569f61-image.png

    可以看到 let temp2= [...this.state.addressData]这段代码实际进行了深拷贝,temp2只是一个指针,指向了与this.state.addressData一样的地址。并没有得到我想要的是指向新的地址的一个新对象。

    使用ES5的写法,const temp2= this.state.addressData.concat( ),结果与上面是一样的。

    如果把上面的数字状态机变量换成数组,如下:

    /*构造函数*/
    constructor(props){
        super(props);
        this.state={
          addressData:[{key:1,contact: 1, address: "测试地址1",isDefault:3},],
          abc:[1,2,3,4,5]
        }
      }
    
    /*按钮点击函数*/
    onClickDefaultAddress(index){
        let temp2= [...this.state.addressData];
        temp2[0].address="临时变量改变地址";
    
        let temp_abc = this.state.abc;
        temp_abc[0]=0;
        console.log(temp_abc, this.state.abc);
        console.log(temp2[0].address, this.state.addressData[0].address);
    
      }
    

    打印的结果是这样的:0_1539245547256_9ca91fc0-d3cb-43e6-90a1-60368d8f0f21-image.png
    这样的数字数组又可以进行浅拷贝。这个结果可是说是非常恶心了。
    难道是我的使用方法不对?有没有人解答一下怎么浅拷贝这个对象数组状态机。



  • 你自己的论述矛盾挺多的,我从头简单说下吧

    1. 在JS中,除了数字、字符串、布尔、null、undefined以外的都是引用类型数据,包括对象、数组、函数
    2. 引用类型的数据用等号就是浅拷贝。对于数组来说,[...]和concat都是深拷贝
    3. 那么为啥深拷贝看起来还是可以修改原先的数据?

    把你的装有对象的数组比作超市里五包装的红烧牛肉面数组就是五小包外面的大包装,你深拷贝数组的操作,实际是把大包装换掉——然而里面的五小包面,仍然是原来的五小包面啊!

    1. 但是数字数组又不一样了。因为数字不是引用型数据,所以数字数组就只有一层包装。
    2. 彻底的深拷贝,就是要把所有的包装都换掉。当然我们几乎不会用到这种显然特别消耗的操作。
    3. react/react native中讨论这个的意义在于如何快速获知数据的变化。当我们根据自己的需要setState的时候,是否考虑过其内部如何知道我们改了哪些数据呢?一个可能令人震惊的事实是,标准的component压根就不检查你改过什么数据,始终重新rerender,哪怕你setState是完全一模一样的数据,这样来保证100%在任何情况下都可以刷新。但是在react native的列表组件或是react的purecomponent中,由于对渲染性能要求较高,它会对数据源进行检查,怎么检查呢?用===号检查。这样的问题就在于,如果你数据源是一个数组,你对数组内部直接修改,其包装上是看不出变化的。
    4. 最直接的实践原则就是,任何时候碰到一个有包装的数据,先换包装再修改。而且是只换你需要换的包装,更深的不管。如果你要给数组增删数据,ok,那就只换数组大包装。如果你要修改数组内部的元素,那么就得先换大包装再换小包装,最后修改。


  • 像FlatList的套路就是这样
    0_1539249231291_flatlist.png

    第一行换大包装,第三四行换小包装,最后放回列表里,于是它看到外包装换了,然后里面某个元素的包装也换了,于是进行对应的最小的更新