[리액트공부] 12. immer

kkado·2022년 8월 5일
0

리다기

목록 보기
13/16
post-thumbnail

이전 챕터에서 성능을 최적화하는 과정에서 불변성을 유지하면서 업데이트하는 것이 왜 중요한지 알게 되었다.

여기서 배열이나 객체를 복사하고 싶을 때 전개 연산자를 활용하면 간단하게 복사하고 새로운 값을 덮어쓸 수 있다고 하였으나, 얕은 복사를 사용하기 때문에 객체의 구조가 겹겹이 싸여 있으면 이를 업데이트하는 것이 매우 어렵다.

이 상황에서, immer 라는 라이브러리를 사용하면 구조가 복잡한 객체도 매우 쉽고 짧은 코드를 활용해서 불변성 유지 업데이트를 할 수 있다.


먼저 immer를 사용하지 않고 불변성을 유지하면서 값을 업데이트하는 컴포넌트를 작성해 본다.

import React, { useCallback, useRef, useState } from "react";

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

  const onChange = useCallback(
    e => {
      const {name, value} = e.target;
      setForm({
        ...form,
        [name] : [value]
      });
    },
    [form]
  );

  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };

      setData({
        ...data,
        array: data.array.concat(info)
      });

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

  const onRemove = useCallback(
    id => {
      setData({
        ...data,
        array: data.array.filter(info => info.id !== id)
      });
    }, [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 사용

예시 코드 #1

import produce from 'immer';
const nextState = produce(originalState, draft => {
	daft.somewhere.deep.inside = 5;
}

produce라는 함수는 두 가지 파라미터를 갖는다. 첫 번째 파라미터는 수정하고 싶은 상태이고, 두 번째 파라미터는 상태를 어떻게 업데이트할지 정의하는 함수이다.

예시 코드 #2

import produce from 'immer';

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

const nextState = produce(originalState, draft => {
  const todo = draft.find(t => t.id === 2);
  todo.checked = true;

  draft.push({
    id: 3,
    todo: "일정 관리 앱에 immer 적용하기",
    checked: false,
  });

  draft.splice(draft.findIndex(t => t.id === 1), 1);
})

immer을 App.js에 적용시켜 보자.

import React, { useCallback, useRef, 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,
  });

  const onChange = useCallback(
    (e) => {
      const { name, value } = e.target;
      setForm(
        produce(form, (draft) => {
          draft[name] = value;
        })
      );
    },
    [form]
  );

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username,
      };

      setData(
        produce(data, (draft) => {
          draft.array.push(info);
        })
      );

      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;

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

이전 챕터에서 useState의 함수형 업데이트에 대해 알아보았다. 아래 예시 코드

const [number, setNumber] = useState(0);
const onIncrease = useCallback(
  () => setNumber(prevNumber => prevNumber + 1), 
  [],
);

그리고 produce 함수를 호출할 때 첫 번째 파라미터가 originalState가 아니라 함수 형태라면, 업데이트 함수를 반환하게 된다.

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

  const onChange = useCallback(
    (e) => {
      const { name, value } = e.target;
      setForm(
        produce((draft) => {
          draft[name] = value;
        })
      );
    },
    []
  );

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username,
      };

      setData(
        produce((draft) => {
          draft.array.push(info);
        })
      );

      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>
  );
};

produce 함수의 파라미터를 함수 형태로 나타내면 코드가 더 깔끔해질 수 있다.

정리

컴포넌트의 상태 업데이트가 까다로울 때 사용하면 좋은 라이브러리인 immer에 대해 알아보았다. 어디까지나 편의를 위한 라이브러리이므로, 오히려 사용하는 것이 불편하다면 사용하지 않아도 무방하다.

profile
베이비 게임 개발자

0개의 댓글