3 minute read

immer 란?

immer(이머)
💡 JavaScript에서 불변성을 유지하면서 상태를 간편하게 업데이트할 수 있도록 도와주는 라이브러리.
💡 immer는 복잡한 중첩된 객체나 배열을 다룰 때 코드를 더 읽기 쉽고 유지보수하기 쉽게 만든다.

불변성 재학습

예제1 (불변성 지키기 기본)

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

object.b = 3;

⚠️ 이렇게 직접 수정하면 안되고 !

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

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

💡 이렇게 얕은복사로 사용해서 불면성을 지키며 새로운 객체를 만들고 수정해야한다.

예제2 (불변성 지키기 배열)

const todos = [
  {
    id: 1,
    text: '할 일 #1',
    done: true
  }}
];
const inserted = todos.concat({
  id: 3,
  text: '할 일 #3',
  done: false
});

💡 배열도 concat 을 사용해서 불면성을 지키며 새로운 객체를 만들고 수정해야한다.
💡 push, splice 등의 함수를 사용하거나 n 번째 항목을 직접 수정하면 안된다.

예제2 (불변성 지키기 배열 심화)

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

💡 위 코드에서 posts 배열 안의 id 가 1 인 post 객체를 찾아서, comments 에 새로운 댓글 객체를 추가해줘야 한다면?

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

// 방법2
const info = {
  id: 2,
  text: '새로운 댓글'
};

const nextState = {
  ...state,
  posts: state.posts.map(post =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat(info)
        }
      : post
  )
};

💡 위 처럼 불변성 하나 지키는데 진짜 복잡하다.
💡이럴 때, immer 라는 라이브러리를 사용하면 엄청 편하게 불변성 조작이 가능하다.

yarn add immer 설치

yarn add immer

import produce from 'immer';

💡코드의 상단에서 immer 를 위 처럼 사용한다.
💡보통 produce 라는 이름으로 불러온다.

Immer(param1:상태, param2:함수) 사용법(1)

immer (param1:obj, param2:func) 함수

  1. 💡param1: 수정하고 싶은 상태
  2. 💡param2: 어떻게 업데이트하고 싶을지 정의하는 함수를 넣는다.
  3. 💡불변성 신경쓰지 않고 업데이트 가능하다. 끝.

예시 코드

import produce from "immer";

const originalState = [
  {
    id: 1,
    todo: "전개 연산자와 배열 내장 함수로 분변성 유지하기",
    chekcked: true,
  },
  {
    id: 2,
    todo: "immer로 불변성 유지하기",
    checked: false,
  },
];

const nextState = produce(originalState, (draft) => {

  // id가 2인 항목의 checked 값을 true로 설정
  const todo = draft.find( (t) => t.id === 2);  // id로 항목찾기
  todo.checked = true;
  // 혹은 draft[1].checked = true;

  //배열에 새로운 데이터 추가
  draft.push({
    id: 3,
    todo: "일정 관리 앱에 immer 적용하기",
    checked: false,
  });

  // id= 1인 항목을 제거하기
  draft.splice(
    draft.findIndex((t) => t.id === 1),
    1
  );
});

Immer(param1:함수) 사용법(2)

immer (param1:func) 함수

  1. 💡param1: 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환.

예시 코드

import produce from "immer";

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

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

console.log(nextState);

onChange() 함수

/* AS-IS */
const onChange = useCallback(
    e => {
        const { name, value } = e.target;
        setForm({
            ...form,
            [name]: [value]
        });
    },
    [form]
);
/* TO-BE */
const onChange = useCallback(e => {
  const { name, value } = e.target;
  setForm(
    produce(draft => {
      draft[name] = value;
    })
  );
}, []);

onSubmit() 함수

/* AS-IS */
// form 등록을 위한 함수
const onSubmit = useCallback(
    e => {
        e.preventDefault();
        // 갱신할 값
        const info = {
            id: nextId.current += 1,
            name: form.name,
            username: form.username
        };
        // array 에 새 항목 등록
        setData({
            ...data,
            array: data.array.concat(info)
            /*
            // 이렇게도 가능 !
            array: data.array.concat({
                id: nextId.current += 1,
                name: form.name,
                username: form.username
            })
            */
        });
        // form 초기화
        setForm({
            name: '',
            username: ''
        });
    }, [data, form.name, form.username]
);

/* TO-BE */
const onSubmit = useCallback(
  e => {
    e.preventDefault();
    // 갱신할 값
    const info = {
      id: nextId.current += 1,
      name: form.name,
      username: form.username
    };
    // array 에 새 항목 등록
    setData(
      produce(draft => {
        draft.array.push(info);
      })
    );
    // form 초기화
    setForm({
      name: '',
      username: ''
    });
  },
  [form.name, form.username]
);

onRemove() 함수

/* AS-IS */
const onRemove = useCallback(
    id => {
        setData({
            ...data,
            array: data.array.filter(info => info.id !id)
        });
    }, [data]
);

/* TO-BE */
const onRemove = useCallback(
  id => {
    setData(
      produce(draft => {
        draft.array.splice(draft.array.findIndex(info => info.id === id), 1);
      })
    );
  },
  []
);

splice()
💡 시작인덱스 부터 ~ 지정 갯수만큼 제거하고 새로운 배열로 반환

  • param1: 시작 인덱스
  • param2: 제거할 요소의 개수

예제

const fruits = ['apple', 'banana', 'orange', 'grape', 'kiwi']; 

const removedFruits = fruits.splice(1, 2);

// 수행 후 
console.log(fruits);
결과: ['apple', 'grape', 'kiwi'] 

console.log(removedFruits);
결과: ['banana', 'orange']

immer 사용시 주의

무조건 사용은 지양한다.
💡 immer를 사용한다고 해서 무조건 코드가 간결한건 아니다.
💡 onRemove의 경우 배열 내장 함수 filter를 사용하는 것이 코드가 더 깔끔하므로 , 굳이 immer를 적용할 필요❌
💡 immer는 불변성을 유지하는 코드가 복잡할 때만 사용해도 충분하다.

Tags:

Categories:

Updated:

Leave a comment