Today I Learned ... react.js
🙋♂️ Reference Book
🙋 My Dev Blog
리액트를 다루는 기술 DAY 11
- 일정 관리 App 성능 최적화 (2) 불변성 유지 - immer 라이브러리
$ yarn create react-app immer-prac$ yarn add immerimport { 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를 한단계마다 해줘야함)
import produce from 'immer';
const nextState = produce(originalState, draft => {
  draft.somewhere.deep.inside = 5; // 바꾸고 싶은 값 변경
})produce 함수
1. originalState = 수정하고 싶은 state
2. callback = state를 어떻게 업데이트할지 정의하는 함수.
produce 함수가 불변성 유지를 대신 해주면서 새로운 상태 생성함.-> 불변성에 신경쓰지 않는 것처럼 작성하지만, 불변성 관리는 제대로 해줌.
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)
});
위 예시를 참고하여 아까 작성했던 App 컴포넌트를 수정해보자.
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 변화 가능.
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;
      }))
  }, []
  )setValue(value+1)을 setValue(prevValue => prevValue + 1)로 고친것과 비슷한 맥락임.