본문 바로가기
Frontend/React

[React] immer를 사용해 쉽게 불변성 유지!

by 디스코비스킷 2023. 12. 28.
반응형

개요

불변성을 유지하면서 상태를 업데이트해야하는데 객체의 구조가 엄청나게 깊어지면 전개 연산자와 배열의 내장함수 만으로는 객체를 복사하고 새로운값을 덮어쓰는것을 매번하기가 힘들고 가독성이 좋지 않음.
immer라는 라이브러리를 사용하면 구조가 복잡한 객체도 매우 쉽고 짧은 코드를 사용하여 불변성을 유지하면서 업데이트 해 줄 수 있음.

immer 설치와 사용법

프로젝트 준비

$ yarn create react-app immer-tutorial
$ cd immer-tutorial
$ yarn add immer

immer 적용전 불변성 유지

import logo from './logo.svg';
import './App.css';
import {useCallback, useRef, useState} from "react";

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>
      <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 사용법

import produce from 'immer';
const nextState = produce(originalState, draft => {
  // 바꾸고 싶은 값 바꾸기
  draft.somewhere.deep.inside = 5;
})
  • produce라는 함수는 두가지 파라미터 받음
    1. 수정하고싶은 상태
    2. 상태를 어떻게 업데이트할지 정의하는 함수
  • produce 첫번째 함수가 함수일때는 업데이트함수를 반환한다.

immer 적용

  const onChange = useCallback(
    e => {
      const {name, value} = e.target;
      setForm(
        // {...form,
        // [name]: [value]}
        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(
        // {...data,
        // array: data.array.concat(info)}
        produce(data, draft => {
          draft.array.push(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)}
        produce(data, draft => {
          draft.array.splice(draft.array.findIndex((info) => info.id === id), 1)
        })
      );
    },
    [data]
  )

객체안에 있는 값을 직접 수정하거나, 배열에 직접적인 변화를 일으키는 push, splice등의 함수를 사용해도 무방하다.

immer를 사용한다고 꼭 간결해지는것은 아니므로 불변성을 유지하는 코드가 복잡해질때만 사용해도 충분하다. onRemove같은경우는 filter라는 배열내장함수를 사용하는것이 더 깔끔하므로 immer를 사용하지 않아도 된다.

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

setState시 새로운 상태를 파라미터로 넣는 대신,
상태 업데이트를 어떻게 할지 정의해주는 업데이트 함수를 넣을 수 있다.
이를 함수형 업데이트라한다.

예시

const [number, setNumber] = useState(0);
const onIncrease = useCallback(
    () => setNumber(prevNumber => prevNumber + 1)
,[]);
  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)
        })
      );
    },
    []
  )

정리

컴포넌트의 상태 업데이트가 까다로울때 사용하면 좋다.
상태관리 라이브러리인 리덕스와 함께 사용하면 코드를 매우 쉽게 작성 할 수있다.
만약 immer를 사용하는것이 더 불편하게 느껴진다면 사용하지 않아도 좋다.

반응형

최근댓글

최근글

© Copyright 2023 jngmnj