[TIL] immutability (불변성)

·2023년 11월 2일
1

TIL

목록 보기
22/85
post-thumbnail

일반적으로 데이터 변경에는 두 가지 방법이 있다.

  1. 데이터의 값을 직접 변경하는 것. (BAD)
  2. 변경 값을 가진 새로운 사본으로 데이터를 교체하는 것. (GOOD)

1️⃣ 객체 변경을 통해 데이터 수정하기 (mutable) 👎

var player = {score: 1, name: 'Jeff'};
player.score = 2;
// 이제 player는 {score: 2, name: 'Jeff'}입니다.

2️⃣ 객체 변경 없이 데이터 수정하기 (imutable) 👍

var player = {score: 1, name: 'Jeff'};
var newPlayer = {...player, score: 2};
// 이제 player는 변하지 않았지만 newPlayer는 {score: 2, name: 'Jeff'}입니다.

최종 결과는 동일하지만, 직접적인 객체 변경이나 기본 데이터의 변경을 하지 않는다면 (불변성을 유지한다면) 몇가지 이점을 얻을 수 있다!

  1. 복잡한 특징을 단순하게 만듦
  2. 변화를 감지함
    • 객체는 참조형 데이터이므로 불변성을 띄지 않는다. 그러므로 객체가 직접적으로 수정될 수 있다.
    • 따라서 복제가 가능한 객체에서 변화를 감지하는 것은 어렵다. (참조하고 있는 주소값은 변화X)
    • 방법 2의 불변 객체 사용 시 참조하고 있는 불변 객체가 이전 객체와 다르다면 객체는 변한 것이므로 변화를 감지하기 쉽다.
  3. React에서 리렌더링 하는 시기를 결정함
    • 불변성의 가장 큰 장점은 React에서 순수 컴포넌트를 만드는 데 도움을 준다는 것이다.
    • 변하지 않는 데이터는 변경이 이루어졌는지 쉽게 판단할 수 있으며 이를 바탕으로 컴포넌트가 리렌더링할지를 결정할 수 있다.

불변성 (immutability) 이란?

👉 불변성 : 한 번 생성된 원본의 데이터는 변경시키지 않고, 데이터 변경 필요 시 새로운 사본을 만드는 디자인 패턴

불변성❌ (mutable)

  • 원본 데이터가 변할 수 있다는 의미
    • ex) 배열의 push 메서드
const arr = [1,2,3,4,5];
arr.push(6);

console.log(arr); // [1,2,3,4,5,6]
// push 메소드는 원본 배열인 arr의 값 자체를 변경시킨다.

불변성⭕️ (immutable)

  • 원본 데이터가 변할 수 없다는 의미
    • ex) 배열의 map 메서드
const arr = [1,2,3,4,5];
arr.map(el => el * 2); // [2,4,6,8,10] 이 리턴됨.

console.log(arr); // [1,2,3,4,5]
// map 메소드는 원본 배열인 arr의 값을 변경시키지 않는다.

[참고]
배열 메서드가 mutable 한지, immutable 한지 확인할 수 있는 사이트
https://doesitmutate.xyz/

React에서 불변성을 지키는 것이 왜 중요한가?

1. React Life Cycle의 리렌더링 규칙

  • React는 state변경 전, state변경 후의 값을 얕은 비교 하여 다른 경우에만 리렌더링 한다.
    • cf) 얕은 비교 : 참조값(object, array)의 경우 주소끼리 비교하고, 원시값(number)의 경우 데이터 자체끼리 비교하는 것.
  • 따라서 참조형 데이터(ex. object)의 불변성을 유지해주어야 리렌더링 시기를 잘 조절 할 수 있다.
// mutable
const [state, setState] = useState({a: 1, b: 2});
// 초기 state {a: 1, b: 2} 는 참조값으로 0x1234 와 같은 주소값을 가리킴.

state.a = 10; // state 값을 직접 변경해도 state는 여전히 0x1234
setState(state); // 기존 state 와 변경요청 온 state 모두 0x1234 이므로 리렌더링이 발생하지 않음
// mutable
function App() {
	// obj 식별자는 MHx0001 주소가 할당되었다고 가정.
	const [obj, setObj] = useState({
		name: "hyewon",
		age: 21
	}); // obj: MHx0001 => {name: "wonjang", age: 21}
	
	return (
		<div>
			<div>{obj.name}</div>
			<button onClick={()=> {
				obj.name = 'hyetwo'; // side Effect 발생 가능성
				console.log(obj);
				setObj(obj); // obj는 여전히 MHx0001 의 주소를 가리킴.
			}}></button>
		</div>
	)
}

2. Side Effect

  • Object에 접근하여 직접 데이터를 변경할 경우 (obj.name = "kim";) 이 객체와 연결되어 있는 다른 데이터가 변경됨에 따른 예상치 못한 부작용(bug)을 일으킬 수 있다.
  • Object 내부 데이터에 직접 변경을 가하는 방식으로 개발할 경우, 명령형(절차적) 프로그래밍을 하게 되어 규모있는 프로젝트를 만들 시 유지보수가 어려워질 수 있다. (스파게티 코드)

불변성을 지키는 수단

1. Spread Operator (...)

얕은 복사를 손쉽게 구현할 수 있다.

const [obj, setObj] = useState({
	name: "James",
	age: 30
})

const increaseAge = () => {
	setObj({...obj, age: obj.age + 1});
  // setObj({name: "James", age: 30, age: obj.age + 1});
  // object의 특성상 key는 중복이 안되며 중복된 경우에는 뒤에 있는 것으로 앞에 있는 것을 덮어 씌운다.
}

그러나 Depth가 깊은 객체라면 다소 가독성이 떨어지고 복잡해보일 수 있다.

const [obj, setObj] = useState({
	name: "James",
	contact: {
		email: "abc@gmail.com",
		phone: "010-1234-5678"
	}
})

const changeEmailTo = (newEmail) => {
	setObj({ ...obj, contact: {...obj.contact, email: newEmail} })
}

이때 사용할 수 있는 것이 immer 라이브러리 이다.

2. 불변성 지원 라이브러리 (immer.js)

depth가 깊은 중첩 객체인 경우, 불변성 여부를 점검하기 어렵기 때문에
mutable한 문법으로도 불변성을 지킬 수 있는 immer 라이브러리 사용을 권장한다. npm i immer

👉 immer 라이브러리 공식문서 https://immerjs.github.io/immer/

import { produce } from "immer";

const [obj, setObj] = useState({
	name: "James",
	contact: {
		email: "abc@gmail.com",
		phone: "010-1234-5678"
	}
})

const changeEmailTo = (newEmail) => {
	setObj(
		produce(draftObj => {
			draftObj.contact.email = newEmail;
		})
	)
}
// 깊은 복사X 수정이 발생한 대상만 복사, 나머지는 유지 -> 메모리 절약 + 불변성 유지

순수함수와 불변성

💡 순수 함수란?

  • 하나 이상의 인자를 받고, 인자를 변경하지 않고 참조하여 새로운 값을 반환하는 함수
  • 즉, 같은 input이 전달되면 항상 동일한 output을 반환하는 함수
  • ex) 순수 함수
// 매개변수를 복사한 값을 변경하는 순수함수
const addSixPure = (arr) => {
  // 펼침 연산자로 새로운 배열에 6 추가
  newArr = [...arr, 6];
  return newArr;
};
  • ex) 비순수 함수
const num_arr = [1, 2, 3, 4, 5];
// 매개변수의 값을 직접 변경하는 비순수함수
const addSixImpure = (arr) => {
  // 매개변수에 직접 6 추가
  arr.push(6);
  return arr;
};
  • React의 함수 컴포넌트에서 input은 props와 state.
  • 함수 컴포넌트는 동일한 input(props와 state)에는 항상 동일한 UI(return JSX)를 보장해야 한다.
  • state 뿐만 아니라 props 역시 불변성을 지켜야만 순수 함수(컴포넌트)라고 할 수 있다.
  • 순수 함수를 사용하면 예측 가능하고 유지보수성이 좋은 코드를 만들 수 있다. -> 강력 권장
profile
느리더라도 조금씩, 꾸준히

0개의 댓글