TIL 22-04-29 (2)

thisisyjin·2022년 4월 29일
0

TIL 📝

목록 보기
32/113

React.js

Today I Learned ... react.js

🙋‍♂️ Reference Book

🙋‍ My Dev Blog


리액트를 다루는 기술 DAY 11

  • 일정 관리 App 성능 최적화 (2) 불변성 유지 - immer 라이브러리

immer 라이브러리

💡 for. 불변성 유지

새 프로젝트 생성

$ yarn create react-app immer-prac

immer 설치

$ yarn add immer

App.js 작성

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

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

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

  const onSubmitForm = 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={onSubmitForm}>
        <input name='username' placeholder='아이디' value={form.username} onChange={onChangeInput} />
        <input name='name' placeholder='이름' value={form.name} onChange={onChangeInput} />
        <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;
  • form에서 아이디/이름 입력시 리스트에 추가되고, (ul>li)

  • 각 항목 클릭시 삭제되는 간단한 컴포넌트.

  • 여기서는 spread(...) 연산자와 배열 내장 함수(concat, filter 등)을 이용하여 간단하게 불변성 유지 가능.

  • 하지만, state가 더 복잡한 배열이라면 힘들어질 수 있음.
    (예- 객체 안에 객체 안에 객체라면? spread를 한단계마다 해줘야함)


immer 사용법

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

produce 함수
1. originalState = 수정하고 싶은 state
2. callback = state를 어떻게 업데이트할지 정의하는 함수.

  • 두번째 인수인 콜백에서 원하는 값을 변경하면,
    produce 함수가 불변성 유지를 대신 해주면서 새로운 상태 생성함.

-> 불변성에 신경쓰지 않는 것처럼 작성하지만, 불변성 관리는 제대로 해줌.

immer 예시

import produce from 'immer';

const originalState = [
  {
    id: 1,
    todo: '청소하기',
    checked: true,
  },
  {
    id: 2,
    todo: '리액트 공부하기',
    checked: false,
  },
];

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

  draft.push({
    id: 3,
    todo: 'scss 공부하기',
    checked: false,
  });
    
    draft.splice(draft.findIndex(t => t.id ===1), 1)
});
  • 이전에는 spread(...) 연산자를 써서 복사본을 만들었었지만,
  • 지금은 그냥 produce()로 수정할 state를 설정하고, 함수를 정의한다.
  • concat()대신 push를 쓸 수 있다.
  • filter()대신 splice를 쓸 수 있다.

위 예시를 참고하여 아까 작성했던 App 컴포넌트를 수정해보자.

App.js 수정

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

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

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

  const onSubmitForm = 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={onSubmitForm}>
        <input name='username' placeholder='아이디' value={form.username} onChange={onChangeInput} />
        <input name='name' placeholder='이름' value={form.name} onChange={onChangeInput} />
        <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의 produce로 상태 수정시 객체 안에 값을 직접 수정하거나, 배열에 직접적인 변화를 일으키는
push, splice 등을 사용해도 된다.

  • spread(...)나 배열 메서드들로 불변성 유지를 하지 않아도 쉽게 불변성 유지 + state 변화 가능.
  • 단, 위에서 onRemove는 splice보다는 filter 메서드를 사용하는 것이 더 간결하므로, 상황에 따라
    복잡한 경우에만 immer을 사용하자!

함수형 setState + immer

  • immer의 produce 함수를 호출할 때, 첫 번째 파라미터는 originalState였다.
  • 만약 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환한다.
const update = produce(draft => {
  draft.value = 2;
});

const originalState = {
  value: 1,
  foo: 'bar'
};

const nextState = update(originalState);
/* produce(
(originalState) => draft => {
  draft.value = 2;
}))
*/


const nextState = update(originalState);

- 수정 전

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

- 수정 후

const onChangeInput = useCallback(
    e => {
      const { name, value } = e.target;
      setForm(produce(draft => {
        draft[name] = value;
      }))
  }, []
  )
  • produce의 두번째 인자만 작성하면 된디고 생각하자.
  • setValue(value+1)setValue(prevValue => prevValue + 1)로 고친것과 비슷한 맥락임.
  • 더이상 state인 form에 의존하지 않으므로, useCallback의 두번째 인자를 빈 배열[ ] 로 한다.
profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글