React 불변성

fe_sw·2022년 8월 1일
0

React

목록 보기
3/10
post-thumbnail

리액트와 불변성의 연관 관계는 리액트가 지향하는 함수형 프로그래밍의 특징에서 발견할 수 있다.

함수형 프로그램밍의 특징 중 하나가 순수함수를 사용하는 것인데,
여기서 순수함수란 동일한 매개변수를 넣었을 때 동일한 리턴값을 출력하는 함수이다.

동시에 순수함수는 외부의 값을 변경하는 사이드 이펙트가 일어나지 않는 조건을 지키는 함수를 뜻한다.
여기서 외부의 값을 변경하지 않는다는 부분이 불변성과 깊은 연관을 가진다

불변성 이해하기

//원시타입

let string = 'data1' // 1. string: 'data1'가 메모리 영역1에 등록됩니다. 
string = 'data2' // 2. string: 'data2'가 메모리 영역2에 등록됩니다.

위의 코드는 string변수에 'data1'값에서 'data2'값으로 변경시켰는데,
이것은 위에서 말한 불변성을 어긴것처럼 보이지만, 실상은 그렇지 않다.

사실 각 문자열값은 각각의 메모리 영역을 차지한다.
기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고,메모리 영역2에 'data2'를 새로 할당한다.
즉, 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당된다.

//참조타입

let array = [1, 2, 3, 4] // 메모리영역 1
array.push(5) // 메모리영역 1 
array = [1, 2, 3, 4, 5] // 메모리영역 2 (새로운 참조값)

array.push(5)는 원본데이터를 수정함으로써 불변성을 지켜주지 않은 것이되고,
array = [1, 2, 3, 4] 는 새로운 배열 [1, 2, 3, 4, 5]을 할당하고 새로운 참조값을 만들어주어 불변성을 지켜준 것이된다.

즉, 불변성의 진짜 의미는 메모리 영역에서 값을 변경할 수 없다는 의미이다.

왜 리액트에서 불변성을 지켜야하나?

리액트에서 불변성을 지켜주는 이유는 리액트가 상태 업데이트를 하는 원리 때문이다.
리액트는 상태값을 업데이트 할 때 얕은 비교를 수행한다.
즉 객체의 속성 하나하나를 비교하는게 아니라 참조값만 비교하여 상태 변화를 감지한다.

이런 이유로 배열이나 객체를 업데이트 할때 setState([...state, newState]), setState({...state, [key]: value}) 이런식으로 배열이나 객체를 새로 생성해서 새로운 참조값을 만들어서 상태를 업데이트 한다.

불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것이다.
즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있다.

다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일인데,
만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문이다.

결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있다.

불변성을 지키면서 얻는 이점

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

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

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

원시타입은 애시당초 불변성 특징을 가지고 있지만,참조타입인 객체나 배열의 경우 새로운 값을 변경할 때 원본 데이터가 변경이 된다(불변성이 지켜지지 않는다).

이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있다. 프로그래밍의 복잡도도 올라간다.

불변성 유지하며 상태값 CRUD하기

CREATE


const users = [
	{id: 1, name: '김한울', userName: 'toycrane'},
	{id: 2, name: '홍길동', userName: 'kill'},
	{id: 3, name: '브로디', userName: 'brody'},
  {id: 4, name: '캐리', userName: 'carry'},
]

const newUser = {id: 5, name: '사울', userName: 'saul'};

// 잘못된 예시
// 기존의 users 객체에 newUser를 추가하여 불변성이 깨진다)
// newUsers에는 기존의 users의 객체 주소가 담기게 된다.
const newUsers = users.push(newUser);

// 정상적인 예시
// ... 비구조할당으로 새로운 list를 만들고, 해당 리스트 맨 뒤에 newUser를 추가하였다.
// newUsers에는 새롭게 생성된 리스트의 주소가 담기게 된다.
const newUsers = [...users, newUser]

READ,DELETE


const users = [
	{id: 1, name: '김한울', userName: 'toycrane'},
	{id: 2, name: '홍길동', userName: 'kill'},
	{id: 3, name: '브로디', userName: 'brody'},
  {id: 4, name: '캐리', userName: 'carry'},
]

// 정상적인 예시
const filteredUsers = users.filter(user => user.id === 1);
console.log(filteredUsers);
// [{id: 1, name: "김한울", userName: "toycrane"}]

UPDATE


const users = [
	{id: 1, name: '김한울', userName: 'toycrane'},
	{id: 2, name: '홍길동', userName: 'kill'},
	{id: 3, name: '브로디', userName: 'brody'},
  {id: 4, name: '캐리', userName: 'carry'},
]

// 정상적인 예시
updatedUsers = users.map(user => (user.id === 1) ? {...user, name: "토이"} : user)
console.log(updatedUsers);
// [{id: 1, name: "토이", userName: "toycrane"},
// {id: 2, name: "홍길동", userName: "kill"},
// {id: 3, name: "브로디", userName: "brody"},
// {id: 4, name: "캐리", userName: "carry"}]

0개의 댓글