React中的浅复制与深复制

React中的浅复制与深复制

学习前端不到一年,在编写react框架下的代码时,经常会遇到明明已经改变了props页面却没有重新渲染的问题。于是为自己整理了一下关于react中的浅复制与深复制的区别以及实现方式,希望对刚刚入门前端的童鞋们也有所帮助。

浅复制

首先我们通过下面一个例子来理解一下什么是浅复制。

/*代码一*/
let obj = {a: 1};
let newObj = obj;
newObj.a = 2;

console.log(obj.a);
//log输出为 2

我们都知道 javascript 存储对象是只存地址的,在上面的代码中 newObj = obj 其实是将 obj 对象所指向的地址赋给 newObj ,也就是说这步操作后 obj 和 newObj 指向了同一块内存地址,所以我们对 newObj.a 赋值其实等同于对 obj.a 进行赋值。所以浅复制可以理解为只复制了一个内存地址。

在我们写代码的时候最常用的就是浅复制,比如我们经常会写下面的代码。

/*代码二*/

/*......*/

this.state = {
    id : 1,
    value : "test"
}

/*......*/

render(
    const { id, value } = this.state;
    return(
        <div> id: { id }, value: { value } div> ) )

这里的 const { id, value } = this.state 其实就是一次浅复制, 我们通常用于在渲染的时候提供一些变化的值,而不需要我们在render中对某个变量进行其他操作。所以在明确只是浅复制的情况下,尽量使用const定义变量,因为const定义的变量不能赋值或更改,这样就避免了后面不小心改变了该变量而引起的问题了。浅复制的好处就是可以有效的节约内存地址,避免不必要的内存浪费。

回到代码一,如果不希望只对 obj 复制一个内存地址,那么就要给 newObj 分配一个新的内存地址,然后进行赋值。

/*代码三*/
let obj = {a: 1};

//分配一个新地址给newObj
let newObj = {};
//赋值
newObj = obj;
newObj.a = 2;
console.log(obj.a);
//log输出为 1,obj的值没有改变

这里可以看作一次深复制,我们获得了一个新的对象以及新的内存地址,之后对newObj的操作不会影响到obj,可以大胆放心的操作。

深复制(deepCopy)

代码三的例子是一次显而易见的深复制,先分配一个内存地址然后再赋值。除了这种方法,我们还可以使用es6中的Object.assign()方法实现深复制。

/*代码四*/
let obj = {a: 1, b:{ c: 2}};
let newObj = Object.assign({},obj);

newObj.a = 2;
newObj.b.c = 3;

console.log(obj.a);
//log输出为 1,obj的值没有改变
console.log(obj.b.c)
//log输出为 3,obj的值改变

可以看到对 newObj.a 赋值后obj并没有改变,然而对 newObj.b.c 赋值之后, obj的值却改变了。这是因为 Object.assign() 方法只能深复制第一层的变量,所以第二层其实是一次浅复制。我们需要对obj.b也用一次Object.assign()才能完成一次完整的深复制。如果obj里面有很多层,就要循环嵌套使用Object.assign()方法很多次。代码三中的方法也只能深复制一层。

在ES7中可以利用 … 对对象解构并且对对象中需要深复制的变量进行拷贝。

/*代码五*/
let obj = {a:1,b:2};

let newObj = {...obj,a:2};//对a深复制
newObj.b = 3;//对b浅复制

console.log(obj);
//{a:1,b:3}
console.log(newObj);
//{a:2,b:3}

在reducer中经常会用到这种方式来更新我们的state,从而达到重新渲染的目的。

/*代码六*/
const initialState = {
    count: 1,
    value: { name : "Michael" }
}
function reducer(state = initialState, action){
    switch(action.type){
        case ADD_COUNT:
        return { ...state, count: state.count + 1 };
        case CHANGE_NAME:
        return { ...state, value : { name : "Lyle" } }
        default:
        return state;
    }
}

state & props

改变state需要用到 setState() 方法,setState() 方法属于深复制,最常用的写法就是

/*代码七*/
this.state = {
    value: { a: 1 }
}
const { value } = this.state;//浅复制
value.a = 2;
console.log(this.state.value.a);//输出2,但dom不更新
this.setState({ value });//dom更新

这里 value.a = 2 虽然已经改变了state中value的值,但由于是浅复制,新旧value指向的是同一块内存地址,组件更新时(state,props改变)默认只比较新旧对象的内存地址是否一致,如果一致则不更新。同理,如果在reducer中,直接对当前的state进行修改并返回props,相应的调用该props的组件不会更新渲染。

/*代码八*/
const initialState = {
    count: 1
}
function reducer(state = initialState, action){
    switch(action.type){
        case ADD_COUNT:
        state.count += 1;
        return { ...state }; //state改变,用到该state的组件不更新渲染
        default:
        return state;
    }
}

基于组件更新的原理,即比较新旧state或props是否指向同一块内存地址,如果是则不更新,如果不是则更新。也就是说就算是两个对象的值相等但不指向同一地址,dom也会重新渲染。这并不是我们想要看到的,我们需要的是当props的值改变的时候dom重新渲染。我们可以在shouldComponentUpdate()方法里面定义dom是否更新的条件,如 if ( props === nextProps ) return true。

完全的深复制

之前谈到的深复制基本是只能复制一层变量,或者必须嵌套着复制多层变量。如果想要更方便的完全复制一个对象,我们可以使用以下方法。

1.使用JSON.stringify()和parse()方法,如:

const newValue = JSON.parse(JSON.stringify(value));

2.lodash —— .clone(obj, true) / .cloneDeep(obj)

总结

1.在reducer中更新state的时候尽量确保每次都返回一个新(深复制)的对象;
2.在常用的组件中使用shouldComponentUpdate()方法提高渲染效率;

你可能感兴趣的:(前端)