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(
        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()方法提高渲染效率;

React中的浅复制与深复制 学习前端不到一年,在编写react框架下的代码时,经常会遇到明明已经改变了props页面却没有重新渲染的问题。于是为自己整理了一下关于react中的浅复制与深复制的区别以及实现方式,希望对刚刚入门前端的童鞋们也有所帮助。
受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。譬如你只是做ListView这样简单的数据显示,将数据拍出来,那么for循坏与 {} 就足够了,但后台系统存在大量报表,不同的表单联动,缺了受控组件真的不行。 受控组件与非受控组件是React处理表单的入口。从React的思路来讲,作者肯定让数据控制一切,或者简单的理解为,页面的生成与更新得忠实地执行JSX的指令。 但是表单元素有其特殊之处,用户可以通过键盘输入与鼠标选择,改变界面的显示。界面的改变也意味着有一些数据被改动,比较明显的是input的 val
react hook使用到深拷贝( let tempBlockData = […blockData]; ),是将state值重新赋值,再进行操作。之所以使用到深拷贝,是为了解决useState定义的state值无法直接更新和操作,所以,为了直接操作state的blockData,先进行深拷贝,然后再用对应的setState进行更新。 什么是深拷贝,什么是浅拷贝? 假设将A复制给B,即B=A,如果B的改变,A也会改变,就是浅拷贝,如果A没有改变,就是深拷贝,(其实深拷贝只针对较为复杂的object类型数据
1. 性能意义:保持state不变这个约束引导我们使用局部更新对象的方法,这样会可以非常有效地提高react或其他显示框架的渲染效率。我们先来看看为了保持数据不变性,要怎么对state做更新,以我们的苹果篮子state为例: 例子:通知开始摘苹果:apple/BEGIN_PICK_APPLE 为了保证每个版本的state不变性,我们有两种实现方式:“复制”,“复制”。我们来看两种模
1.state值不可直接修改,且在state操作过程,也需要确认state原来的值没被修改。 const [arr,setArr] = useState([1,2,3]) const [obj,setObj] = useState({a:1,b:2}) 1.需求:向arr数组加入一个数字4元素,向对象obj加一个属性c:3 //错误写法: arr.push(4) obj.c = 3 // 正确写法 setArr([...arr,4]) setObj({...obj,c:3}) 2.需求 操作过程
今天遇到一个问题,我更新了数组里面的数据,但是页面并没有检测到页面的改变。而导致这个原因是,就是浅拷贝深拷贝的问题。 当我们针对数组对象嵌套比较时,采用浅拷贝的方式就会出现上述情况。一般也是针对数据结构毕竟时,采用深拷贝 下面举个针对多层嵌套数组对象的栗子 const arr = [ [{a:'1'},{b:'2'},{c:'3'}], [{a:'1'},{b:'2'},{c:'3'}], [{a:'1'},{b:'2'},{c:'3'}] 发布时间:2017-03-09 11:13:46 作者:杨川宝 这篇文章主要为大家详细介绍了react性能优化达到最大化的方法,一步一步优化react性能的过程,告诉大家使用immutable.js的必要性,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 一行代码胜过千言万语。这篇文章呢,主要讲述我一步一步优化rea...
props 1、在子组件,通过 props 来定义接收时候的名字 props: ['list'] 2、我们在父组件传递值的时候,要在使用子组件的时候,通过 属性名 = 值 的方式来传递,其 属性名必须是在子组件 props 定义好的,值也要注意,如果值是来源于模型话,必须进行绑定 $emit 1、在子组件,通过触发自定义事件 this.$emit('自定义事件名称',值),进行传值,第一个参数必须写,第二个参数是可 function copy(e) { let transfer = document.createElement('input'); document.body.appendChild(transfer); transfer.value = t