본문 바로가기
JavaScript

불변성은 왜 지켜야할까?

by oagree0123 2022. 4. 27.

불변성 (Immutability)

불변성은 값이나 상태를 변경할 수 없는 것을 의미합니다.

프로그래밍에서 불변성은 데이터 원본의 훼손을 막는 것을 의미합니다.

간단히 말하자면, 어떤 값을 직접적으로 변경하지 않고 새로운 값을 만들어내는 것입니다.

 

자바스크립트에서는 원시타입은 불변성을 지니지만, 객체타입은 값이 변할 수 있습니다.

 

원시 타입

원시 타입에는 Boolean, Number, String, null, undefined, Symbol이 있습니다.

 

먼저 코드를 보겠습니다.

let str = "example1";
str = "example2";

console.log(str) // example2

위에서는 원시타입은 값을 변경할 수 없다고 했는데 위와 같은 결과가 나왔습니다.

사실, 위 과정은 값이 변경된 것이 아니라 새로 선언된 값이 메모리에 저장되고
변수가 새로운 메모리를 가리키는 것입니다.

 

코드를 하나 더 보자면

let name = 'KIM';
let new_name = name;
name = 'LEE';

console.log(new_name); // KIM
console.log(name); // LEE

위와 같이 결과가 나온 이유는, 2번 줄에서 new_name이 name이 가리키고 있는 

메모리를 같이 가리키고 있기 때문입니다.

 

객체 타입

var person = {
    name: 'KIM',
    age: 29,
}
var new_person = person;
person.name = 'LEE';

console.log(person.name); // 'LEE' 
console.log(new_person.name); // 'LEE'

위 코드를 보면 객체 타입은 원시 타입과 다르게 이름이 모두 변경된 것을 볼 수 있습니다.

객체는 객체 내부의 값을 변경하면 객체를 참조하고 있는 다른 값들도 다 같이 변경됩니다.

 

불변성은 왜 지켜야할까?

리액트는 상태값을 업데이트 할 때 얕은 비교를 수행합니다.

리액트는 객체의 속성 하나하나를 비교하는게 아니라 참조값만 비교하여 상태 변화를 감지합니다.

 

얕은 비교란 간단히 말하자면 객체, 배열, 함수와 같은 참조 타입들을 실제 내부 값까지 비교하지 않고 동일 참조인지(동일한 메모리 값을 사용하는지)를 비교하는 것을 뜻합니다.

 

예시를 보자면,

  • 우리가 컴포넌트를 리렌더링 해야하는 상황이 있다고 가정하고, 타입이 배열인 state를 바꿉니다.
  • 이때, state.push(1)을 통해 state 배열에 직접 접근하여 요소를 추가합니다.
  • 우리는 push 전과 다른 값이라고 생각하지만, 리엑트는 state라는 값은 새로운 참조값이 아니기 때문에 이전과 같은 값이라고 인식하고 리렌더링 하지 않습니다.

 

불변성의 장점

1. 효율적인 상태업데이트 (얕은 비교 수행)

얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인합니다. 

얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있습니다.

 

2. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성.

원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 새로운 값을 변경할 때 원본 데이터가 변경이 됩니다(불변성이 지켜지지 않습니다). 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있습니다. 프로그래밍의 복잡도도 올라갑니다.

불변성을 지키면 이를 방지할 수 있습니다.

 

불변성을 지키는 방법

1. Spread 문법

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

// 참조 타입
const [person, setPerson] = useState({ name: '', age: 29 })
setState({...person, name: 'KIM'})

setUsers(
  users.map(user =>
    user.id === id ? { ...user, name: "KIM" } : user
  )
);

 

2. immer 사용

import produce from "immer";

const user = [
  {
    name: "KIM",
    age: 29
  },
  {
    name: "LEE",
    age: 32
  }
];

const new_user = produce(user, draft => {
  draft.push({ name: "PARK" });
  draft[1].age = 28;
});

댓글