프론트엔드 개발자들이 사용하는 연산자가 있습니다.
바로 Spread 연산자입니다.
하지만 데이터 구조가 깊어지고 복잡해질수록 코드는 더러워지며, 소위 말하는 Spread 연산자 지옥이 생성됩니다.
이번 글에선 이러한 Spread 연산자에게 고통받았을 사람들을 위해 더욱 간편한 immer이라는 라이브러리를 소개해볼 것입니다.

Spread 연산자?
먼저 Spread 연산자가 무엇이고, 이것이 왜 안 좋은 지부터 설명해 드리겠습니다.
Spread 연산자는 JS에서 객체나 배열의 값을 바꿀 때, 원본을 유지하기 위해 사용되는 연산자입니다.
이는 프론트엔드 개발자들이 매우 자주 사용하고 애용하는 연산자 중 하나이죠.
하지만 위에서 말했듯, 데이터 구조가 깊어진다면 코드가 매우 더러워지는 문제가 발생합니다.
const state = {
user: {
name: "Gemini",
posts: [ { id: 1, title: "Hello" } ]
}
};
// 포스트 제목 하나 바꾸려는데... 이 난리를 쳐야 함
const nextState = {
...state,
user: {
...state.user,
posts: state.user.posts.map(post =>
post.id === 1 ? { ...post, title: "Immer is Great" } : post
)
}
};
위 코드를 보시면 한눈에 봐도 보기 힘들고, 가독성이 매우 떨어진다는 것을 알 수 있습니다.
이를 해결하기 위해 나온 것이 바로 immer입니다.
immer? 넌 누구냐
자 본격적으로 immer 라이브러리에 대해 알아보겠습니다.
immer은 불변성을 신경 쓰지 않고 코드를 짜도, 알아서 불변하게 만들어주는 도구입니다.
쉽게 말해 Spread 연산자를 덕지덕지 붙일 필요 없이 원래 사용하던 대로 값을 바꿔도 문제가 없다는 소리죠!
import { produce } from "immer";
const nextState = produce(state, draft => {
draft.user.posts[0].title = "Immer is Great";
});
위 코드를 보시면 아까 Spread를 사용한 코드와 다르게 가독성이 압도적으로 좋아진 게 보이실 겁니다.
그럼 이 코드의 동작 원리는 무엇이고, 사용하는 방법은 대체 무엇일까요?
동작 원리
immer의 핵심은 바로 produce 함수와 draft라는 개념입니다.
- produce
- 보통 produce(baseState, recipe)로 선언됩니다.
- baseState는 원본 데이터, recipe 함수는 우리가 어떻게 데이터를 바꿀지 적어두는 레시피입니다.
- draft
- draft는 recipe 함수 자리에 위치합니다.
- baseState와 똑같이 생겼지만, JS의 Proxy 기술로 감싸 활동을 전부 기록합니다.
- 위 코드를 예시로 들면 title을 immer is Great로 바꾼다는 걸 proxy를 통해 draft에 저장하는 것입니다.
사용 방법
그렇다면 이 좋은 라이브러리를 사용하는 방법은 무엇일까요?
1. 라이브러리 설치
npm install immer
# 또는
yarn add immer
2. 기본 구조: produce 함수 사용
가장 많이 사용되는 패턴입니다.
import { produce } from "immer";
const baseState = [
{ id: 1, title: "CORS 공부하기", done: true },
{ id: 2, title: "Immer 공부하기", done: false }
];
const nextState = produce(baseState, draft => {
// 1. 값 직접 수정 (Mutate)
draft[1].done = true;
// 2. 새로운 항목 추가
draft.push({ id: 3, title: "블로그 포스팅 완료", done: false });
});
3. React useState와 함께 쓰기
리액트의 상태 업데이트 함수에 immer를 적용하는 것이 가능합니다.
useState에 produce를 넣으면 코드의 가독성이 더욱 좋아집니다.
import { useState } from "react";
import { produce } from "immer";
function TodoApp() {
const [todo, setTodo] = useState({
user: "Gemini",
list: [{ id: 1, text: "CORS 에러 잡기", active: false }]
});
const onToggle = (id) => {
// 업데이트 함수 안에 produce를 바로 사용!
setTodo(
produce(draft => {
const item = draft.list.find(t => t.id === id);
item.active = !item.active; // 그냥 이렇게 바꿔도 불변성이 유지됩니다.
})
);
};
// ... 생략
}
주의점
이 immer를 사용하는 것에도 주의점이 존재합니다.
- 함수 내부에서만 수정해야 합니다.
- draft를 produce 함수 밖으로 꺼내서 수정하려고 하면 에러가 발생합니다.
- 아무것도 반환하지 마세요.
- 레시피 함수(draft => {...}) 안에서 return을 하지 않아도 괜찮습니다.
- 단, 새로운 객체로 아예 갈아치울 때는 return을 사용해야 합니다.
이와 같은 주의점만 조심하면 immer는 여러분에게 더욱 간편한 상태관리를 제공합니다!
여러분들도 Spread 말고 immer 라이브러리를 사용하여 가독성을 높여보세요.
'Front-End' 카테고리의 다른 글
| DOM, 이건 알고가자 (1) | 2026.04.08 |
|---|---|
| WebRTC, 화상통화 구현이 어려운 당신을 위한 (3) (0) | 2026.03.10 |
| WebRTC, 화상통화 구현이 어려운 당신을 위한 (2) (0) | 2026.03.06 |
| WebRTC, 화상통화 구현이 어려운 당신을 위한 (1) (1) | 2026.01.12 |
| NX와 함께하는 모노레포 알아가기 (0) | 2026.01.08 |