[리액트스타터2] 12장. immer를 사용하여 더 쉽게 불변성 유지하기

PYOUNANI·2022년 12월 28일
0

React

목록 보기
8/9
post-thumbnail

앞선 단원에서 전개 연산자와 배열의 내장 함수를 사용하여 배열 혹은 객체를 복사하고 새로운 값을 덮어 쓰는 것을 배웠습니다. 하지만 객체의 구조가 깊어진다면 불변성을 유지하면서 업데이트하는 것은 힘든 일입니다.

이러한 상황에 immer라이브러리를 사용해볼 수 있습니다. 이번 단원에서는 다음과 같은 순서로 진행합니다.

  1. immer 설치
  2. immer 사용법
  3. App 컴포넌트에 immer 적용하기
  4. useState의 함수형 업데이트와 immer 함께 쓰기

I. immer 설치 및 사용법 알아보기

1. immer 설치

$ yarn add immer

2. immer 사용법

immer라이브러리를 사용하면 불변성을 유지하면서 간단하게 값을 업데이트할 수 있습니다.

import produce from 'immer';
const nextState = produce(originalState, draft => {
  // 바꾸고 싶은 값 바꾸기
  draft.somewhere.deep.inside = 5;

produce 함수의 첫 번째 파라미터는 수정하고 싶은 상태이고, 두 번째 파라미터는 상태를 어떻게 업데이트할지 정의하는 함수입니다.두번째 파라미터로 전달되는 함수 내부에서 원하는 값을 변경하면, produce 함수가 불변성 유지를 대신해 주면서 새로운 상태를 생성합니다.

immer 라이브러리의 핵심은 '불변성에 신경 쓰지 않는 것처럼 코드를 작성하되 불변성 관리는 제대로해 주는 것'입니다. 깊은 곳에 위치하는 값을 바꾸는 것뿐만 아니라 배열을 처리할 때도 쉽게 처리할 수 있습니다.

import produce from 'immer';

const originalState = [
  {
    id: 1,
    todo: '전개 연산자와 배열 내장 함수로 불변성 유지하기',
    checked: 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);
});

3. App 컴포넌트에 immer 적용하기

// App.js

import { useRef, useCallback, useState } from 'react';
import produce from 'immer';

const App = () => {
  const nextId = useRef(1);
  const [form, setForm] = useState({ name: '', username: '' });
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });

  // input 수정을 위한 함수
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(
      produce(form, draft => {
        draft[name] = value;
      })
    );
  }, [form]);

  // form 등록을 위한 함수
  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };

      // array 에 새 항목 등록
      setData(
        produce(data, draft => {
          draft.array.push(info);
        })
      );

      // form 초기화
      setForm({
        name: '',
        username: ''
      });
      nextId.current += 1;
    },
    [data, form.name, form.username]
  );

  // 항목을 삭제하는 함수
  const onRemove = useCallback(
    id => {
      setData(
        produce(data, draft => {
          draft.array.splice(draft.array.findIndex(info => info.id === id), 1);
        })
      );
    },
    [data]
  );

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          name="username"
          placeholder="아이디"
          value={form.username}
          onChange={onChange}
        />
        <input
          name="name"
          placeholder="이름"
          value={form.name}
          onChange={onChange}
        />
        <button type="submit">등록</button>
      </form>
      <div>
        <ul>
          {data.array.map(info => (
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default App;

immer를 사용하여 컴포넌트 상태를 작성할 때는 객체 안에 있는 값을 직접 수정하거나, 배열에 직접적인 변화를 일으키는 push, splice 등의 함수를 사용해도 무방합니다. 물론 immer를 사용한다고 해서 무조건 코드가 간결해지는 것은 아닙니다. 예를 들어 onRemove의 경우에는 배열 내장 함수 filter를 사용하는 것이 코드가 더 깔끔하므로, 굳이 immer를 적용할 필요가 없습니다.

4. useState의 함수형 업데이트와 immer 함께 쓰기

immer에서 제공하는 produce 함수를 호출할 때, 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환합니다.

const update = produce(draft => {
draft.value = 2;
};
const originalstate = {
value: 1,
foo: 'bar',
);
const nextState = update(originalState);
console.log(nextState); // { value: 2, foo: 'bar' }

App.js 에 적용시켜보도록 하자.

// App.js

import { useRef, useCallback, useState } from 'react';
import produce from 'immer';

const App = () => {
  const nextId = useRef(1);
  const [form, setForm] = useState({ name: '', username: '' });
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });

  // input 수정을 위한 함수
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(
      produce(draft => {
        draft[name] = value;
      })
    );
  }, []);

  // form 등록을 위한 함수
  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };

      // array 에 새 항목 등록
      setData(
        produce(draft => {
          draft.array.push(info);
        })
      );

      // form 초기화
      setForm({
        name: '',
        username: ''
      });
      nextId.current += 1;
    },
    [form.name, form.username]
  );

  // 항목을 삭제하는 함수
  const onRemove = useCallback(
    id => {
      setData(
        produce(draft => {
          draft.array.splice(draft.array.findIndex(info => info.id === id), 1);
        })
      );
    },
    []
  );

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          name="username"
          placeholder="아이디"
          value={form.username}
          onChange={onChange}
        />
        <input
          name="name"
          placeholder="이름"
          value={form.name}
          onChange={onChange}
        />
        <button type="submit">등록</button>
      </form>
      <div>
        <ul>
          {data.array.map(info => (
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default App;

문제

1.기존에는 (), ()를 사용하여 배열, 객체의 데이터들을 원하는 값으로 새로 지정할 수 있었다.
2. immer 라이브러리를 사용하면 ()하며 값을 업데이트를 간단하게 할 수 있다.
3. produce 함수에서 첫 번째 파라미터는 ()상태이다.
4. produce 함수에서 두 번째 파라미터는 () 함수이다.
5. immer를 사용할 때 배열에 직접적인 변화를 일으키는 (), () 등의 함수를 사용해도 무방하다.
6. immer를 사용한다고 코드가 항상()해지는 것은 아니며, 불변성 유지하는 코드가 ()때만 사용하면 된다.
7. immer 속성과 ()를 함께 사용하면 코드를 더욱 깔끔하게 만들 수 있다.
8. immer 라이브러리를 사용하기위해 필요한 import 코드를 작성하여라.
9. id가 1인 항목의 checked 값을 true로 설정하는 함수를 작성하여라.

import produce from 'immer';

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

//코드 작성

정답
1. 전개 연산자, 배열의 내장 함수
2. 불변성 유지
3. 수정하고 싶은
4. 상태를 어떻게 업데이트할지 정의하는
5. push, splice
6. 간결, 복잡
7. useState
8.

import produce from 'immer';
const nextState = produce(originalState, draft => {
  const todo = draft.find(t => t.id === 1); 
  todo.checked = true;
});

0개의 댓글