
함수형 코딩책에서 사용하는 개념어들은 이렇게 바꿔서 생각하면 쉽다.
액션 -> 부수효과 , 계산 -> 순수함수, 데이터 -> 불변성
이번에 읽은 챕터에서는 더 다양한 이야기를 하고 있지만,
책을 읽으면서 가장 중요하다고 생각되는 불변성과 그에 따른 복사 개념들을 정리해 보려고 한다.
책에 있는 내용과 내가 찾아본 내용을 추가해 작성했다!
🚀 불변성은 왜 중요할까?
책에서 액션을 줄이는것 만큼이나 중요한 것은 불변성을 유지하는 것이 중요하다고 말하고 있다.
불변성이란 말 그대로, 값을 바꾸지 않고 새 값을 만든다는 것인데 예를 들면,
📌 가변 방식
const arr = [1, 2, 3];
arr.push(4); //원본이 바뀜
const user = { name: 'Lee', age: 30 };
user.age = 31; //원본 객체가 바뀜
📌 불변 방식
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 원본은 그대로, 새 배열 생성
const user = { name: 'Lee', age: 30 };
const newUser = { ...user, age: 31 }; //원본은 그대로, 새 객체 생성
그래서 이게 왜 중요할까?
🚀 중요한 5가지 이유
1. 이전 상태가 그대로 남기 때문에 → 상태 추적이 쉬워진다
const before = { count: 0 };
const after = { ...before, count: before.count + 1 };
before 은 변하지 않아, 상태가 어떻게 변해왔는지 비교하거나, 로그를 찍기 쉬워진다.
2. 상태 히스토리를 안전하게 저장하기 때문에 → Undo(되돌리기) / Redo(되돌리기 복원) / 시간여행 디버깅 구현 가능
const stateHistory = [];
let currentState = { count: 0 };
stateHistory.push(currentState);
currentState = { ...currentState, count: currentState.count + 1 };
stateHistory.push(currentState);
불변상태를 계속 쌓아주면, 이전상태로 돌아가는 것이 가능하다.
이것 덕분에 Redux DevTools같은 시간여행 디버깅 도구가 존재 가능하다.
3. 함수가 예측 가능해지면서 → 버그 가능성이 줄어든다
4. React의 얕은 비교(Shallow Compare) 활용 → 성능 최적화 가능
const obj1 = { a: 1 };
const obj2 = { a: 1 };
obj1 === obj2; // false (내용은 같아도 다른 참조)
const same = obj1;
same === obj1; // true (같은 참조)
React 컴포넌트의 성능 최적화할 때, useMemo, shouldComponentUpdate, React.memo 같은 기능을 쓸 수 있는 이유는 뭐다?
바로 얕은 비교가 가능하기 때문이다.
불변성을 지키면 상태가 바뀌었는지 안 바뀌었는지를 참조값만 비교 해서 알 수 있다.
5. 공유 데이터가 안전하게 유지됨 → 병렬 처리 환경에서도 안전
이건 JS보다는 Rust, Java 같은 언어에서 더 부각되지만,
공유 상태를 수정하지 않기 때문에 경합 조건(race condition) 걱정 없이 코드를 짤 수 있다.
Java, Rust, C 같은 언어에서는 여러 스레드가 동시에 메모리를 건드릴 수 있다.
쉽게 말하면 여러 사람이 동시에 같은 데이터를 만져도 안전하다~
🚀 Copy-on-Write 패턴
불변성을 지키면서, 상태를 안전하게 변경하는 실용적인 방법으로,
아래 삼단계를 따르는 아주 단순하면서 강력한 전략이다
1. 원본 데이터를 복사한다.
2. 복사본을 수정한다.
3. 수정된 복사본을 반환한다.
const updateUserName = (user, newName) => {
const copy = { ...user };
copy.name = newName;
return copy;
};
✅ 불변성을 지키면서 상태를 안전하게 바꾸는 함수형 코딩 패턴
복사 전략
결국 불변성을 유지하려면 결국에는 복사를 잘해야 한다는 건데...
하지만 아무렇게나 하는 복사는 코드의 복잡성을 늘리기 때문에 상황에 맞는 복사 전략을 따라야 한다.
🚀 얕은 복사 (Shallow Copy)
한 단계만 복사되고, 중첩 객체는 참조만 복사된다.
... 연산자 또는 Object.assign 사용
const user = { name: 'Lee', info: { age: 30 } };
const copy = { ...user };
copy.info.age = 31;
console.log(user.info.age); //31 (원본도 바뀜)
✅ 빠르지만 중첩 구조에선 주의 필요!
🚀 깊은 복사 (Deep Copy)
모든 중첩 구조를 완전히 복사한다.
structuredClone, JSON.parse(JSON.stringify(...)) 등 사용
const user = { name: 'Lee', info: { age: 30 } };
const deepCopy = structuredClone(user);
deepCopy.info.age = 31;
console.log(user.info.age); // 30 (원본 그대로)
✅ 안전하지만 얕은 복사보단 비용이 크고, 구조가 복잡할수록 느릴 수 있음
🚀 방어적 복사 (Defensive Copy)
외부에서 받은 객체를 직접 사용하지 않고 복사해서 저장하는 방식으로, 외부에서 상태를 오염시키는 것을 방지한다.
API, 상태관리 라이브러리 등에서 자주 사용된다.
const setUser = (user) => {
state.user = { ...user }; // 얕은 복사로 방어
};
✅ 외부 객체가 내부 상태를 오염시키는 것을 방지하지만, 많은 데이터를 복사해 비용이 큼
장바구니 업데이트 예시 코드
📌 가변 코드
const cart = ['아이폰', '아이패드'];
function addItem(cart, item) {
cart.push(item); //원본 cart 직접 수정
return cart;
}
const updatedCart = addItem(cart, '맥북');
console.log(cart); // ['아이폰', '아이패드', '맥북'] //원본 변경
✅ 불변성 유지 코드
const cart = ['아이폰', '아이패드'];
function addItem(cart, item) {
return [...cart, item]; // 원본을 복사 -> 새로운 배열 생성
}
const updatedCart = addItem(cart, '맥북');
console.log(cart); // ['아이폰', '아이패드'] // 원본은 그대로
console.log(updatedCart); // ['아이폰', '아이패트', '맥북']
원본 배열을 건드리지 않고, 새로운 배열을 만들어 리턴해 불변성을 유지
'FE > BOOK' 카테고리의 다른 글
[함수형코딩]WEEK4 계층형 설계- 직접 구현 (0) | 2025.04.03 |
---|---|
[함수형코딩]WEEK2 함수형 사고: 더 좋은 액션 만들기 (1) | 2025.03.19 |
[함수형 코딩] WEEK1 액션, 계산, 데이터 (2) | 2025.03.12 |
[HTTP 완벽가이드] 18장 웹 호스팅 (0) | 2025.02.10 |
[HTTP 완벽가이드] 17장 내용 협상과 트랜스 코딩 (2) | 2025.02.10 |