Immer.js는 Js에서 불변 상태(immutable state)를 쉽게 다룰 수 있게 해주는 작은 라이브러리이다.
주로 React 애플리케이션에 서 사용되며, 상태를 직접 변경하는 것처럼 보이지만 실제론 상태의 불변성을 유지해준다.
Immer.js는 "producer"라는 개념을 사용한다.
프로듀서는 기본 상태를 입력받아 새로운 상태를 생성하는 함수이다.
이 함수 내에서 상태를 직접 변경하는 것처럼 코드를 작성할 수 있지만, Immer.js는 기본 상태를 변경하지 않고 새로운 상태를 반환한다.
import produce from 'immer';
// 초기 상태
const baseState = {
todos: [
{ id: 1, text: 'Immer.js 배우기', done: false },
{ id: 2, text: 'React에서 Immer.js 사용하기', done: false },
],
};
// 프로듀서 함수
const nextState = produce(baseState, draftState => {
draftState.todos[0].done = true; // 첫 번째 할일을 완료로 표시
});
// const nextState = produce(baseState, ((draftState) => {
// draftState.todos[0].done = true; // 첫 번째 할일을 완료로 표시
// }));
console.log(baseState.todos[0].done); // 출력: false (원본 상태는 변경되지 않음)
console.log(nextState.todos[0].done); // 출력: true (새로운 상태는 변경됨)
위 예제에서 produce함수는 원본 상태 baseState를 입력받고, 변경된 새로운 상태 nextState를 반환한다.
draftState 는 마치 상태를 직접 변경하는 것처럼 다룰 수 있지만, 실제로는 새로운 상태가 생성된다.
아래는 추가 예제이다.
import produce, { original } from 'immer';
const baseState = {
users: [
{ id: 1, name: 'John Doe', posts: [{ id: 1, title: 'Hello World' }] },
{ id: 2, name: 'Jane Doe', posts: [{ id: 2, title: 'Immer is great!' }] }
]
};
const nextState = produce(baseState, draft => {
const user = draft.users.find(user => user.id === 1);
if (user) {
user.posts.push({ id: 3, title: 'Advanced Immer usage' });
}
// Using original to avoid unnecessary updates
const originalState = original(draft);
console.log(originalState === baseState); // true (original 상태는 변경되지 않음)
});
console.log(baseState.users[0].posts.length); // 1 (원본 상태는 변경되지 않음)
console.log(nextState.users[0].posts.length); // 2 (새로운 상태는 변경됨)
해당 라이브러리는 zustand와 react query를 다룰 때 알면 좋다 해서 간단한 예제정도만 가져와보았다.
import create from 'zustand';
import produce from 'immer';
const useStore = create(set => ({
todos: [
{ id: 1, text: 'Immer.js 배우기', done: false },
{ id: 2, text: 'React에서 Immer.js 사용하기', done: false },
],
toggleTodo: (id) => set(state =>
produce(state, draft => {
const todo = draft.todos.find(todo => todo.id === id);
if (todo) {
todo.done = !todo.done;
}
})
),
}));
// 사용 예
const { todos, toggleTodo } = useStore();
toggleTodo(1);
console.log(todos); // 첫 번째 할일의 done 상태가 토글됨
React Query는 서버 상태를 쉽게 관리할 수 있게 해주는 라이브러리임.
Immer.js를 사용하면 캐시된 데이터를 쉽게 불변으로 업데이트할 수 있음.
import { useQueryClient, useMutation } from 'react-query';
import produce from 'immer';
const useUpdateTodo = () => {
const queryClient = useQueryClient();
return useMutation(
async (updatedTodo) => {
// API 요청 예제
const response = await fetch(`/api/todos/${updatedTodo.id}`, {
method: 'PUT',
body: JSON.stringify(updatedTodo),
});
return response.json();
},
{
onMutate: async (updatedTodo) => {
await queryClient.cancelQueries('todos');
const previousTodos = queryClient.getQueryData('todos');
queryClient.setQueryData('todos', oldTodos =>
produce(oldTodos, draft => {
const todo = draft.find(t => t.id === updatedTodo.id);
if (todo) {
todo.text = updatedTodo.text;
todo.done = updatedTodo.done;
}
})
);
return { previousTodos };
},
onError: (err, updatedTodo, context) => {
queryClient.setQueryData('todos', context.previousTodos);
},
onSettled: () => {
queryClient.invalidateQueries('todos');
},
}
);
};
// 사용 예
const { mutate: updateTodo } = useUpdateTodo();
updateTodo({ id: 1, text: 'Immer.js 완벽 이해', done: true });