'리액트를 다루는 기술' 12장, immer를 사용하여 더 쉽게 불변성 유지하기

Jake_Young·2020년 8월 24일
0
post-thumbnail

😎 immer를 설치하고 사용법 알아보기

  • 전개 연산자와 배열의 내장 함수를 사용하면 간단하게 배역 혹은 객체를 복사하고 새로운 값을 덮어 쓸 수 있었다.
  • 하지만 객체의 구조가 엄청나게 깊어지면 불변성을 유지하면서 이를 업데이트하는 것이 매우 힘들다.
  • 예를 들어 아래와 같이 써야하는 것이다.
let nextObject = {
  ..object,
  somewhere: {
    ...object.somewhere,
    deep: {
      ...object.somewhere.deep,
      array: object.somewhere.deep.array.concat(5)
    }
  }
}
  • 기존 처럼 전개 연산자와 배열 내장 함수를 사용하여 불변성을 유지하는 예시는 아래와 같다
// App.js
import React, { useRef, useCallback, useState } from "react";
import "./styles.css";

export default function 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 className="App">
      <form onSubmit={onSubmit}>
        <input
          name="username"
          placeholder="id"
          value={form.username}
          onChange={onChange}
        />
        <input
          name="name"
          placeholder="name"
          value={form.name}
          onChange={onChange}
        />
        <button type="submit">enroll</button>
      </form>
      <div>
        <ul>
          {data.array.map((info) => (
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
  • 이를 immer를 사용하면 훨씬 간결하게 적을 수 있다.
  • 그리고 immer의 사용 예시는 아래와 같다.
import produce from 'immer';
//const nextState = produce(originalState, draft => {
//  draft.somewhere.deep.inside = 5;
//})
const originalState = [
  {
    id: 1,
    todo: "yes",
    checked: true,
  },
  {
    id: 2,
    todo: "no",
    checked: false,
  }
]

const nextState = produce(originalState, draft => {
  
  // 수정
  const todo = draft.find(t => t.id === 2)
  todo.checked = true
  
  // 추가
  draft.push({
    id: 3,
    todo: "half",
    checked: false,
  })
  
  // 삭제
  draft.splice(draft.findIndex(t => t.id === 1), 1)
  
});
  • immer를 이용하여 이전의 상태 관리 방법을 개선하면 그 코드는 아래와 같다.
//...

export default function App() {
  // ...

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

  const onSubmit = useCallback(
    (e) => {
      // ...
      setData(
        **produce(data, (draft) => {
          draft.array.push(info);
        })**
      );
      // ...
    },
    [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]
  );
  // ...
}

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

  • immer에서 제공하는 produce 함수를 호출할 때, 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환한다.
  • 예시 코드는 아래와 같다.
const update = produce(draft => {draft.value=2})
const originalState = {value:1, foo:'bar'}
const nextState = update(originalState)
  • App.js에서 produce 맨 앞의 data 항을 지우면 적용된다.
profile
자바스크립트와 파이썬 그리고 컴퓨터와 네트워크

0개의 댓글