深拷贝和浅拷贝在React的应用

224 阅读4分钟

深拷贝与浅拷贝的区别

前文我们讲到,JavaScript中存在两大数据类型:

  • 基本类型
  • 引用类型

基本类型数据保存在在内存中

引用类型数据保存在内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在

浅拷贝

浅拷贝仅复制对象的第一层属性,对于嵌套的对象和数组,只复制其引用。修改浅拷贝后的嵌套对象会影响原始对象。

浅拷贝的简单实现:

function shallowClone(obj) {
    const newObj = {}; //创建一个空对象newObj,用于存储拷贝的属性。
    for (let prop in obj) {//循环遍历对象obj的所有可枚举属性
        if (obj.hasOwnProperty(prop)) {//检查属性是否是对象obj自己的属性,而不是从原型链继承的属性
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

让我们看一个具体的示例来演示 shallowClone 的工作原理:

const original = {
    name: 'Alice',
    age: 30,
    address: {
        city: 'Wonderland',
        street: 'Rabbit Hole'
    }
};

const cloned = shallowClone(original);

console.log(cloned);
// 输出: { name: 'Alice', age: 30, address: { city: 'Wonderland', street: 'Rabbit Hole' } }

// 修改克隆对象的属性
cloned.name = 'Bob';
cloned.address.city = 'Looking Glass';

console.log(original.name); // 输出: 'Alice',原始对象未受影响
console.log(original.address.city); // 输出: 'Looking Glass',原始对象受影响

更简洁的浅拷贝方法

在现代 JavaScript 中,可以使用 Object.assign 或展开运算符 ... 来实现浅拷贝:

// 使用 Object.assign
const cloned1 = Object.assign({}, original);

// 使用展开运算符
const cloned2 = { ...original };

数组的浅拷贝还有一下几种方式:

const originalArray = [1, 2, 3, { a: 4 }];
//使用slice方法
const shallowCopy = originalArray.slice();

//使用展开运算符 (`...`)
const shallowCopy = [...originalArray];

//使用Array.from方法
const shallowCopy = Array.from(originalArray);

//使用concat方法
const shallowCopy = originalArray.concat();

深拷贝

深拷贝递归地复制对象的所有层级,对于嵌套对象和数组,也会创建新的实例。修改深拷贝后的嵌套对象不会影响原始对象。

const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 3;

console.log(original.b.c); // 2

深拷贝的简单实现

function deepClone(obj, hash = new WeakMap()) {
  // 如果是 null 或者非对象类型,直接返回
  if (obj === null || typeof obj !== 'object') return obj;

  // 处理日期对象
  if (obj instanceof Date) return new Date(obj);

  // 处理正则表达式对象
  if (obj instanceof RegExp) return new RegExp(obj);

  // 处理循环引用
  if (hash.get(obj)) return hash.get(obj);

  // 创建一个新的对象实例
  const cloneObj = new obj.constructor();

  // 将新对象存入 hash 中,处理循环引用
  hash.set(obj, cloneObj);

  // 遍历对象的所有属性,包括不可枚举属性和符号属性
  Reflect.ownKeys(obj).forEach(key => {
    // 递归拷贝属性值
    cloneObj[key] = deepClone(obj[key], hash);
  });

  return cloneObj;
}

React 中的应用

在 React 中,处理状态时需要注意对象和数组的拷贝方式。React 依赖于不可变数据的概念,即状态的修改应该创建新的对象或数组实例,而不是直接修改原来的状态。这样可以确保 React 能够正确地检测到状态变化,并触发重新渲染。

浅拷贝的应用

浅拷贝通常用于简单的状态更新,特别是当状态对象或数组没有嵌套结构时。 示例:

import React, { useState } from 'react';

const App = () => {
  const [state, setState] = useState({ count: 0 });

  const increment = () => {
    setState(prevState => ({
      ...prevState,
      count: prevState.count + 1
    }));
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default App;

在这个例子中,state 对象没有嵌套结构,使用浅拷贝(扩展运算符)来更新状态是安全的。

深拷贝的应用

深拷贝用于处理嵌套结构的状态,确保更新不会影响原始状态对象。

import React, { useState } from 'react';

const App = () => {
  const [state, setState] = useState({ user: { name: 'John', age: 30 } });

  const updateAge = () => {
    setState(prevState => {
      const newState = JSON.parse(JSON.stringify(prevState));
      newState.user.age = 31;
      return newState;
    });
  };

  return (
    <div>
      <p>Name: {state.user.name}</p>
      <p>Age: {state.user.age}</p>
      <button onClick={updateAge}>Update Age</button>
    </div>
  );
};

export default App;

在这个例子中,state 对象具有嵌套结构,因此使用深拷贝来确保更新不会影响原始状态。

使用第三方库进行深拷贝

使用 JSON.parse(JSON.stringify()) 进行深拷贝有其局限性(例如不支持函数undefinedsymbolDate函数等特殊值)。

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

可以使用第三方库如 lodashcloneDeep 方法来进行更可靠的深拷贝。

import React, { useState } from 'react';
import cloneDeep from 'lodash/cloneDeep';

const App = () => {
  const [state, setState] = useState({ user: { name: 'John', age: 30 } });

  const updateAge = () => {
    setState(prevState => {
      const newState = cloneDeep(prevState);
      newState.user.age = 31;
      return newState;
    });
  };

  return (
    <div>
      <p>Name: {state.user.name}</p>
      <p>Age: {state.user.age}</p>
      <button onClick={updateAge}>Update Age</button>
    </div>
  );
};

export default App;

总结

  • 浅拷贝:适用于没有嵌套结构的状态更新,常用方法包括扩展运算符(...)和 Object.assign
  • 深拷贝:适用于嵌套结构的状态更新,确保更新不会影响原始状态。可以使用 JSON.parse(JSON.stringify())lodashcloneDeep 方法。
  • React 中的状态更新:始终确保创建新对象或数组实例来触发正确的重新渲染。

理解深拷贝和浅拷贝的区别以及如何在 React 中正确地更新状态,对于编写健壮和高效的 React 应用非常重要。