KSCOLD LogoKSCOLD
Blog
Feed
Product
Admin Night
Vault
Guestbook
Info

Vault 목록

불변성 유지

연결된 노트와 함께 읽기

불변성 유지

2026년 5월 17일
  • 리액트(React)에서 상태(state)를 업데이트할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 한다.

  • 이렇게 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 불변성을 지킨다고 한다.

  • 불변성이란 메모리 영역의 값을 변경할 수 없는 것이다.

  • 불변성 유지를 해 주어야 나중에 리액트(React) 컴포넌트(Component)의 성능을 최적화할 수 있다.

  • 리액트는 불변성을 지켜줌으로써 효율적인 상태(state) 업데이트를 한다.

  • 리액트는 불변성을 지켜줌으로써 사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지한다.

  • 불변성을 가진 원시 타입(Primitive type)과 달리 참조 타입(Reference Type)의 경우에는 의도적으로 불변성을 지켜주어야한다.

  • 이 때 새로운 주소 값을 가진 객체(Object)를 생성하여 상태를 업데이트 해준다.

  • 확산 연산자(spread operator), map(), filter(), slice(), reduce() 메서드(Method)들을 활용한다.

  • 업데이트가 필요한 곳에서는 아예 새로운 배열(Array) 혹은 새로운 객체(Object)를 만들기 때문에, React.memo()를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지 알아내서 리렌더링(Re-rendering)성능을 최적화해줄 수 있다.

원시 타입(Primitive type)과 참조 타입(Reference Type)의 차이점

  • 원시 타입(Primitive type)은 불변성을 가지고 있다.(값을 변경할 수 없다는 이야기가 아니다.)
  • 대표적인 원시타입인 let string 타입을 통해 예를 들어보자.
JavaScript
  • 변수 string은 data1 -> data2로 값이 변경되었다.
  • 사실 위 문장은 반은 맞고 반은 틀리다.
  • 위 예시를 보면  string 변수는 'data1' → 'data2' 로 값이 변경된 것처럼 보이지만 실제 메모리영역에는 'data1', 'data2' 둘다 존재한다.
  • 메모리 영역이 1~10영역까지 10개가 있다고 가정해보자.
JavaScript
  • 위 예시에서 메모리 영역을 총 2개 사용했다.

  • 변수(Variable) string은 'data1' 였고, 여기에 'data2'를 재할당하였는데 기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고, 메모리 영역2에 'data2'를 새로 할당했다.

  • 즉, 자바스크립트는 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당된다.

  • 이것이 불변성이다.

  • 이번엔 참조 타입을 통해서 불변성에 대해 알아보자.

JavaScript
  • array.push(5)는 원본 배열(Array)을 수정하면서 불변성을 지키지 않고 있고, array = [1, 2, 3, 4]는 원본 배열을 수정하는게 아니라 새 참조값을 가진 새로운 배열 [1, 2, 3, 4]을 할당하여 불변성을 지켜주고 있다.

  • 따라서 정리하자면, 불변성의 진짜 의미는 메모리 영역에서 값이 변하지 않는다라는 의미이다.

JSX

리액트에서 불변성을 지켜야 하는 이유

1. 리엑트의 작동 방식

  • 리액트에서 불변성을 지켜주는 이유는 리액트가 상태(state)를 업데이트를 하는 원리 때문이다.
  • 리액트는 상태값을 업데이트 할 때 얕은 비교(얕은 복사)를 수행한다.
  • 즉 배열(Array)이나 객체(Object)의 속성(Property) 하나하나를 비교하는게 아니라 이전 참조값과 현재 참조값만을 비교하여 상태 변화를 감지한다.
  • 이런 이유로 배열이나 객체를 업데이트 할때 setState([...state, newState]), setState({...state, [key]: value}) 이런식으로 새로운 참조값을 가진 배열(Array)이나 객체(Object)를 생성하는 것이다.
  • 불변성을 지킴으로써 리액트는 상태변화를 감지할 수 있다.

2. 사이트 이펙트 방지

  • 불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것이다.
  • 즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있다.
  • 다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일이다.
  • 만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문이다. (프로그램의 복잡성이 증가할 수 있다.)
  • 결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있다.

정리

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

    • 얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인한다.
    • 얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있다.
  2. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성

    • 원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있다. (불변성이 지켜지지 않을 수 있다.)
    • 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있다.
    • 프로그래밍의 복잡도도 올라간다.
    • 따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있다.

불변성을 키는 방법

  • 확산 연산자(spread operator), map(), filter(), slice(), reduce() 등등 새로운 배열(Array)을 반환하는 메서드(Method)들을 활용하면 된다.
  • splice()는 원본데이터를 변경한다.(주의)
  • setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만 참조타입인 경우에는 새로운 객체(Object)나 배열(Array)을 생성한 후 값을 넣어주어야 한다.
JavaScript

Synapse Map Insight

let string = 'data1' string = 'data2'
let string = 'data1' // 1. string: 'data1'가 메모리 영역1에 등록됩니다. string = 'data2' // 2. string: 'data2'가 메모리 영역2에 등록됩니다.
let array = [1, 2, 3, 4] // 메모리영역 1 array.push(5) // 메모리영역 1 array = [1, 2, 3, 4] // 메모리영역 2 (새로운 참조값)
const array = [1, 2, 3, 4, 5]; const nextArrayBad = array; // 배열을 복사하는 것이 아니라 똑같은 배열은 가리킴 nextArrayBad[0] = 100; console.log(array === nextArrayBad); // 완전이 같은 배열이기 때문에 true const nextArrayGood = [...array]; // 배열 내부의 값을 모두 복사함 nextArrayGood[0] = 100; console.log(array === nextArrayGood); // 다른 배열이기 때문에 false const object = { foo: 'bar', value: 1 } const nextObjectBad = object; // 객체가 복사되지 않고, 똑같은 객체를 가리킴 nextObjectBad.value = nextObjectBad.value + 1; console.log(object === nextObjectBad); // 같은 객체이기 때문에 true const nextObjectGood = { ...object, // 기존에 있던 내용을 모두 복사해서 넣음 value: object.value + 1 // 새로운 값을 덮어씀 console.log(object === nextObjectBad); // 다른 객체이기 때문에 false
// 원시타입 const [number, setNumber] = useState(0) setState(3) // 참조타입 const [person, setPerson] = useState({ name: '', age: 30 }) setState({...person, name: 'pyo'}) // 확산 연산자를 사용하여 불변성을 유지한다.