12.1 immer를 설치하고 사용법 알아보기
- 전개 연산자와 배열의 내장 함수를 사용하면 간단하게 배열 혹은 객체를 복사하고 새로운 값을 덮어 쓸 수 있음
- 하지만 객체의 구조가 엄청나게 깊어지면 불변성을 유지하면서 이를 업데이트하는 것이 매우 힘듬
- immer 라이브러리를 사용하면, 구조가 복잡한 객체도 매우 쉽고 짧은 코드를 사용하여 불변성을 유지하면서 업데이트해 줄 수 있음
- 컴포넌트의 상태 업데이트가 조금 까다로울 때 사용하면 좋음
npm install immer
12.1.1 immer를 사용하지 않고 불변성 유지
App.js
- 폼에서 아이디/이름을 입력하면 하단 리스트에 추가되고, 리스트 항목을 클릭하면 삭제되는 컴포넌트
import { useRef, useCallback, useState } from 'react';
const App = () => {
const nextId = useRef(1);
const [form, setForm] = useState({name:'', username:''});
const [data, setData] = useState({
array: [],
uselessValue: null
});
//input 수정을 위한 함수
const onChange = useCallback (
e => {
const { name, value } = e.target;
setForm ({
...form,
[name] : [value]
});
},
[form]
);
//form 등록을 위한 함수
const onSubmit = useCallback(
e=> {
e.preventDefault();
const info = {
id: nextId.current,
name: form.name,
username: form.username
};
//array에 새 항목 등록
setData({
...data,
array: data.array.concat(info)
});
//form 초기화
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;
12.1.2 immer 사용법
ex)
import produce from 'immer';
const nextState = produce(originalState, draft => {
//바꾸고 싶은 값 바꾸기
draft.somewhere.deep.inside = 5;
})
- produce 함수는 두가지 파라미터를 받음
- 첫 번째 파라미터 = 수정하고 싶은 상태
- 두 번째 파라미터 = 상태를 어떻게 업데이트할지 정의하는 함수
- 두번째 파라미터로 전달되는 함수 내부에서 원하는 값을 변경하면, produce 함수가 불변성 유지를 대신해 주면서 새로운 상태를 생성해 줌
ex)
import produce from 'immer';
//수정하고 싶은 상태
const originalState = [
{
id:1,
todo: '전개 연산자와 배열 내장 함수로 불변성 유지하기',
checked: true,
},
{
id:2,
todo: 'immer로 불변성 유지하기',
checked: false,
}
];
const nextState = produce(originalState, draft => {
//id가 2인 항목의 checked 값을 true로 설정
const todo = draft.find(t => t.id === 2); //id로 항목 찾기
todo.checked = true;
//혹은 draft[1].checked = true;
//배열에 새로운 데이터 추가
draft.push({
id:3,
todo: '일정 관리 앱에 immer 적용하기',
checked:false,
});
//id = 1인 항목을 제거하기
draft.splice(draft.findIndex(t=> t.id === 1),1);
});
12.1.3 App 컴포넌트에 immer 적용하기
App.js
- immer를 사용하여 컴포넌트 상태를 작성할 때는 객체 안에 있는 값을 직접 수정하거나, 배열에 직접적인 변화를 일으키는 push, splice 등의 함수를 사용해도 무방
import React, { useRef, useCallback, useState } from 'react';
import {produce} from 'immer';
const App = () => {
const nextId = useRef(1);
const [form, setForm] = useState({ name: '', username: '' });
const [data, setData] = useState({
array: [],
uselessValue: null
});
// input 수정을 위한 함수
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(
produce(form, draft => {
draft[name] = value;
})
);
}, [form]);
// form 등록을 위한 함수
const onSubmit = useCallback(
e => {
e.preventDefault();
const info = {
id: nextId.current,
name: form.name,
username: form.username
};
// array 에 새 항목 등록
setData(
produce(data, draft => {
draft.array.push(info);
})
);
// form 초기화
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={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;
12.1.4 useState의 함수형 업데이트와 immer 함께 쓰기
- useState의 함수형 업데이트
ex)
const [number, setNumber] = useState(0);
//prevNumbers는 현재 number 값을 가리킴
const onIncrease = useCallback(
()=> setNumber(prevNumber => prevNumber +1),
[],
);
- immer에서 제공하는 produce 함수를 호출할 때, 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환
ex)
const update = produce(draft => {
draft.value = 2;
});
const originalState = {
value:1,
foo: 'bar',
};
const nextState = update(originalState);
console.log(nextState)l //{value:2, foo:'bar'}
- immer의 속성과 useState의 함수형 업데이트를 함께 활용 하면 코드를 더욱 깔끔하게 만들 수 있음
App.js
...
setForm (
produce(draft => {
draft[name] = value;
});
...
setData (
produce(draft => {
draft.array.push(info);
});
...
produce(draft => {
draft.array.splice(draft.array.findIndex(info => info.id === id),1);
'react' 카테고리의 다른 글
[리액트를 다루는 기술] 14장 외부 API를 연동하여 뉴스 뷰어 만들기 (1) | 2023.11.17 |
---|---|
[리액트를 다루는 기술] 13장 리액트 라우터로 SPA 개발하기 (1) | 2023.11.17 |
[리액트를 다루는 기술] 11장 컴포넌트 성능 최적화 (0) | 2023.11.15 |
인프런 - MERN 스택으로 만드는 지도 서비스 (+TypeScript) (2) (0) | 2023.11.10 |
인프런 - MERN 스택으로 만드는 지도 서비스 (+TypeScript) (1) (3) | 2023.11.10 |