📌 useImmer()
: React에서 상태 관리를 쉽게 할 수 있도록 도와주는 Hook
- React의 useState 훅과 Immer 라이브러리가 제공하는 불변 업데이트 패턴을 결합한 커스텀 React 훅
- React 상태 관리를 쉽게 하고, 복잡한 상태 업데이트를 간단하게 할 수 있는 훅
- 중첩된 객체나 배열 상태를 업데이트할 때 유용
- ❓언제 사용하면 좋은가
- 중첩된 구조의 상태를 자주 업데이트하는 경우
- 상태 업데이트 시 불변성을 유지해야 하는 경우
- 코드의 가독성을 높이고 싶을 때
⏩ 장단점
- 장점
- 간단한 상태 관리
: 중첩된 객체와 배열의 상태 업데이트를 간단하게 해준다. - 불변성 자동 관리
: immer가 불변성을 자동으로 관리해 주므로, 직접 불변성을 신경 쓸 필요가 없다.
- 상태가 의도치 않게 변형되는 것을 방지 - 가독성 향상
: 상태 업데이트 로직이 직관적이어서 코드 가독성이 향상된다.
- 간단한 상태 관리
- 단점
- 추가 의존성
: `immer` 라이브러리에 의존하므로, 프로젝트에 추가적인 의존성을 더하게 된다. - 퍼포먼스 오버헤드
: `immer`가 내부적으로 복잡한 상태 객체를 복제하고 불변성을 유지하는 작업을 하기 때문에
경우에 따라 (미미한) 퍼포먼스 오버헤드가 발생할 수 있다.
- 추가 의존성
✨ Immer 라이브러리
: JavaScript의 불변성을 관리하는 라이브러리
- 복잡한 상태 업데이트를 간단하고 직관적으로 할 수 있게 해준다.
- immer는 상태 객체를 "초안(draft)" 형태로 제공하고, 이 초안을 직접 수정할 수 있다.
- 👉 immer는 이 초안을 기반으로 불변성을 유지하는 새로운 상태 객체를 생성한다.
✅ 설치
pnpm add use-immer immer
✅ 사용
1. useImmer 가져오기
import { useImmer } from 'use-immer';
2. useImmer 훅 사용하기
const [state, updateState] = useImmer(initialState);
- state: 현재 상태 값
- updateState: 상태를 업데이트하는 데 사용되는 함수
- initialState: useImmer 훅에 전달하는 초기 상태값
- - 컴포넌트가 처음 렌더링될 때 사용될 상태의 초기값 정의
3. 상태 업데이트(draft)하기
updateState(draft => {
// 여기서 draft 직접 수정
});
- updateState: 함수를 인수로 받는다.
- draft: 임시 사본
- 가변성(Mutability)
- draft는 일반적인 자바스크립트 객체처럼 직접 수정할 수 있다.
- 하지만 실제로는 원본 상태를 변경하지 않는다.
- 프록시(Proxy)
- draft는 실제로 프록시 객체이다.
- 이 프록시는 모든 변경사항을 추적한다.
- 불변성 보장
- draft를 수정하면, Immer가 이를 기반으로 새로운 불변 상태를 생성한다.
- 원본 상태는 변경되지 않는다.
- 가변성(Mutability)
- draft는 상태(state)의 임시 사본이다.
- draft를 직접 수정하면, Immer가 이 수정사항을 기반으로 새로운 불변 상태를 만들어낸다.
const [user, updateUser] = useImmer({
name: '홍길동',
age: 30,
hobbies: ['독서', '운동']
});
// 나이 증가
updateUser(draft => {
draft.age += 1;
});
// 새 취미 추가
updateUser(draft => {
draft.hobbies.push('음악 감상');
});
// 중첩된 객체 업데이트
updateUser(draft => {
if (!draft.address) {
draft.address = {};
}
draft.address.city = '서울';
});
예제 1. Todo 리스트
import React, { useState } from 'react';
import { useImmer } from 'use-immer';
function TodoList() {
// 1. useImmer를 사용하여 todos 상태 관리
const [todos, updateTodos] = useImmer([
{ id: 1, text: '리액트 공부하기', completed: false },
{ id: 2, text: '운동하기', completed: true },
]);
const [newTodo, setNewTodo] = useState('');
// 2. 할 일 추가 함수
const addTodo = () => {
// - draft.push()를 사용하여 새 항목을 직접 추가
updateTodos(draft => {
draft.push({ id: Date.now(), text: newTodo, completed: false });
});
setNewTodo('');
};
// 3. 할 일 완료 상태 토글 함수
const toggleTodo = (id) => {
// - draft에서 해당 항목을 찾아 직접 completed 속성을 변경
updateTodos(draft => {
const todo = draft.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
};
// 4. 할 일을= 삭제 함수
const deleteTodo = (id) => {
// - draft.splice()를 사용하여 항목을 직접 제거
updateTodos(draft => {
const index = draft.findIndex(t => t.id === id);
if (index !== -1) {
draft.splice(index, 1);
}
});
};
return (
<div>
<h1>할 일 목록</h1>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="새 할 일 입력"
/>
<button onClick={addTodo}>추가</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;