깊은복사와 얕은복사, 그리고 React에서의 불변성

김명주·2023년 5월 27일
0
post-custom-banner

깊은 복사와 얕은 복사

  1. 깊은 복사

    • 참조가 아닌 값을 그대로 복사하지만, 같은 값을 가지는 별개의 메모리 공간을 갖는다. 그러므로 한 객체 값의 변경이 다른 객체 값의 변경에 영향을 주지 않는다.
    • 값을 복사한다 하더라도, 인스턴스가 메모리에 새로 생성되지 않는다.
    • 값 자체를 복사하는 것이 아니라 주소값을 복사하여 같은 메모리를 가리킨다.
  2. 얕은 복사

    • 객체를 복사할 때 참조에 의한 할당이 이루어지므로 원본과 같은 메모리 주소를 갖게 되고 이것이 얕은 복사이다.
    • 그러므로 한 변수의 데이터를 변경하면 다른 변수의 데이터의 값도 함께 변경 된다. 즉, 한 데이터를 공유하고 있는 것이다.
    • 복사된 두 객체는 완전히 독립적인 메모리를 차지한다.

자바스크립트는 원시 자료형과 참조 자료형이 있다.

  1. 원시 자료형은 Number, string, boolean, undefined, null, symbol이 있다.
    • 원시 자료형은 값 자체에 대한 변경이 불가능(immutable)하지만, 변수에 다른 데이터를 할당할 수는 있다.
    • 이들은 깊은 복사를 한다. 즉, 불변성을 유지한다.
let a = 1;
let b = a;

b = 2;

console.log(a); // 1
console.log(b); // 2
console.log(a === b); // false
  1. 참조 자료형은 Object나 Array가 있다.
    • 이들은 얕은 복사를 기반으로 한다. 그러므로 가변성을 가지고 있다.
    • 그러므로 객체나 배열의 불변성이 필요한 경우 깊은 복사의 방법을 고민해야 한다.
const obj = { vaule: 1 }
const newObj = obj;

newObj.vaule = 2;

console.log(obj.vaule); // 2
console.log(obj === newObj); // true

React에서 불변성을 지켜야 하는 이유

불변성을 검색해보면 불변성이란 값이나 상태를 변경할 수 없는 것을 의미한다. 불변성의 진짜 의미는 메모리 영역에서 값이 변하지 않는다 이다.
기본적으로 원시 자료형을 제외한 참조형인 Array나 Object는 가변성을 띄고 있다.
리액트에서 불변성을 지켜주는 이유는 리액트가 상태 업데이트를 하는 원리에 있다. 리액트는 상태값을 업데이트 할 때 얕은 비교를 수행한다. 즉, 객체의 속성 하나하나를 비교하는게 아니라 참조값만 비교하여 상태 변화를 감지한다. 따라서 Object or Array의 속성이나 값을 바꾸는 불변성이 없는 변경은 상태가 바뀌었다고 React에서 파악을 못하기 때문에 이런 불변성을 지키는 업데이트가 필요하다.
불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것이다. 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있다.
결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있다.

  1. 효율적인 상태 업데이트 (얕은 비교 수행)
    얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인한다.
    얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있다.
  1. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성.
    원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있다. 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있고, 프로그램의 복잡도 또한 올라갈 수 있다.
    따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있다.

  2. 불변성을 지키는 방법
    spread operator, map, filter, slice, reduce 등등 새로운 배열을 반환하는 메소드들을 활용하면 된다.
    그러나 splice 메소드는 원본 데이터를 변경하므로 사용에 주의가 필요하다.
    setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만 참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 한다.

// 원시타입
const [number, setNumber] = useState(0)
setState(3)

// 참조타입
const [person, setPerson] = useState({ name: '', age: 30 })
setState({...person, name: 'pyo'})
profile
개발자를 향해 달리는 사람
post-custom-banner

0개의 댓글