이다닷

[React] Day 25 - Immer를 사용한 더 쉬운 불변성 관리 본문

React

[React] Day 25 - Immer를 사용한 더 쉬운 불변성 관리

이다닷 2023. 10. 6. 17:11

📢리액트에서 배열이나 객체를 업데이트 해야 할 때에는 직접 수정하면 안되고, 불변성을 지켜주면서 업데이트 해주어야 한다. -> 지금까지 사용했던 확장 연산자를 사용하면 된다.

 

ex) 

const num = {
  a: 1,
  b: 2
};

num.b = 3;

이렇게 하면 안되고, 

const num = {
  a: 1,
  b: 2
};

const nextNum = {
  ...object,
  b: 3
};

이렇게 해주어야 한다. 

 

📌배열도 마찬가지로 항목을 직접 수정하면 안되고, concat, filter, map 등의 함수를 사용해야한다.

 

이 외에 상황에서 데이터의 구조가 조금 까다로워지면 불변성을 지켜가면서 새로운 데이터를 생성해내는 코드가 조금 복잡해질때가 있다.

 

ex)

const state = {
  posts: [
    {
      id: 1,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 1,
          text: '와 정말 잘 읽었습니다.'
        }
      ]
    },
    {
      id: 2,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 2,
          text: '또 다른 댓글 어쩌고 저쩌고'
        }
      ]
    }
  ],
  selectedId: 1
};

const nextState = {
  ...state,
  posts: state.posts.map(post =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat({
            id: 3,
            text: '새로운 댓글'
          })
        }
      : post
  )
};

-> 이렇게 복잡해질 수 있다.

 

📌이럴때, immer이라는 라이브러리를 사용하면 쉽게 구현할 수 있다.

위에 있는 코드를 예시로 바꿔보면, 이렇게 짧아지는 것을 볼 수 있다.

const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

console.log(nextState);

 

우리의 APP.js라는 코드를 변경시켜볼 수 있다.

 

APP.js 변경

import React, { useReducer, useMemo } from 'react';
import UserList from './qoffhvjxm/UserList';
import CreateUser from './qoffhvjxm/CreateUser';
import produce from 'immer';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      });
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      });
    default:
      return state;
  }
}

export const UserDispatch = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const { users } = state;

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser />
      <UserList users={users} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

 

좋은 부분도 있지만, 오히려 immer을 사용해서 복잡해지는 경우도 있다. 그리고, immer은 좋은 라이브러리지만 성능적으로는 immer을 사용하지 않은 코드가 조금 더 빠르다. 따라서 적절하게 사용하면 좋은 라이브러리이다.