Today I Learned ... react.js
🙋♂️ Reference Book
🙋 My Dev Blog
리액트를 다루는 기술 DAY 11
- 일정 관리 App 성능 최적화 (2) 불변성 유지 - immer 라이브러리
$ yarn create react-app immer-prac
$ yarn add immer
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를 한단계마다 해줘야함)
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)
로 고친것과 비슷한 맥락임.