React -Immer.js

박요셉·2024년 5월 29일
0

React

목록 보기
9/15

Immer.js 란 뭘까?

Immer.js는 Js에서 불변 상태(immutable state)를 쉽게 다룰 수 있게 해주는 작은 라이브러리이다.
주로 React 애플리케이션에 서 사용되며, 상태를 직접 변경하는 것처럼 보이지만 실제론 상태의 불변성을 유지해준다.

왜 Immer.js를 사용하나?

  1. 간편함 : 불변 상태를 다루는 것을 간편하게 해준다.
  2. 가독성 : 복잡한 상태 업데이트를 다룰 때 코드의 가독성을 높여준다.
  3. 불변성 유지 : 상태의 불변성을 보장하여 예기치 않은 부작용을 방지한다.

Immer.js는 어떻게 작동하나?

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를 다룰 때 알면 좋다 해서 간단한 예제정도만 가져와보았다.

zustand + immer

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

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 });
profile
개발자 지망생

0개의 댓글