이 글은 Velopert의 "리액트를 다루는 기술"을 참고하였습니다.
1. 불변성 관리
리액트를 다루면서 불변성을 유지하면서 상태를 업데이트하는 것이 중요하단 걸 모든 개발자는 알 것입니다.
이를 위해 전개 연산자 및 배열의 내장 함수를 사용하면 배열이나 객체를 복사하고 새로운 값을 덮어쓸 수 있습니다.
그러나 객체의 구조가 커지고 깊이가 깊어지면 불변성을 유지하면서 업데이트하기가 매우 힘들어집니다. 값 하나를 업데이트하기 위해 수많은 코드를 작성해야 할 수도 있습니다.
이러한 상황에서 immer 라이브러리를 이용하면 구조가 복잡한 객체도 짧은 코드를 사용해 불변성을 유지하면서 업데이트를 할 수 있습니다.
2. immer
Immer은 보다 편리한 방식으로 불변의 상태로 작업을 가능하게 해주는 패키지입니다. copy-on-write 메커니즘을 기반으로 합니다.
기본 아이디어는 모든 변경사항을 currentState의 프록시인 임시 draftState에 반영하는 것입니다.
이후 모든 변경이 끝이 나면 Immer는 draftState에 대한 변경내용을 기반으로 nextState를 생성합니다.
즉, Immer는 현재 상태를 가져와서 변경 사항을 기록하기 위해 draft를 만들고 작업이 끝나면 draft를 토대로 nextState를 만듭니다.
기본적으로 Immer는 다음과 같이 사용합니다.
import produce from 'immer';
const nextState = produce(orgState, draft => {
//값 바꾸기
draft.somewhere.deep.inside = changedValue;
});
procude 함수는 두 파라미터를 받습니다. 첫 번째 파라미터는 수정하고 싶은 상태이며 두 번째 파라미터는 상태를 어떻게 업데이트할지 정의하는 함수 합니다.
draft내부에서 값을 변경하면 produce 함수가 불변성을 유지하면서 상태를 업데이트해 줍니다.
3. 불변성 유지를 위해 Immer를 사용하지 않을 때
다음 예시 코드는 Immer를 사용하지 않고 불변성을 유지할 때의 예시 코드입니다.
import React, {useRef, useCallback, useState} from 'react';
const 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="ID" value={form.userName} onChange={onChange}/>
<input name="name" placeholder="Name" value={form.name} onChange={onChange}/>
<button type="submit">Register</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;
위와 같이 전개 연산자와 배열의 내장 함수를 사용하면 어렵진 않지만 상태가 복잡해지면 귀찮은 작업이 필요하게 될 수 있습니다.
4. 불변성 유지를 위해 Immer를 사용할 때
다음 예시 코드는 Immer를 적용한 코드입니다.
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
});
const onChange = useCallback(e=>{
const {name, value} = e.target;
setForm(
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(
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={onSubmit}>
<input name="userName" placeholder="ID" value={form.userName} onChange={onChange}/>
<input name="name" placeholder="Name" value={form.name} onChange={onChange}/>
<button type="submit">Register</button>
</form>
<div>
<ul>
{data.array.map(info=>(
<li key={info.id} onClick={()=>onRemove(info.id)}>
{info.userName} ({info.name})
</li>
))}
</ul>
</div>
</div>
);
};
Immer는 필수적으로 사용해야 하는 라이브러리는 아닙니다. 사용하는 게 더 불편하다면 사용할 이유가 없습니다. 단지 복잡한 객체의 상태를 업데이트해야 할 때 많은 도움이 되며 생산성을 높여줍니다.
'Programming > JavaScript' 카테고리의 다른 글
Express에서 GraphQL 사용하기: express-graphql (0) | 2020.04.04 |
---|---|
NodeJs - PostgreSql 연동하기. (0) | 2020.04.04 |
[Redux] Redux란? (0) | 2019.10.04 |
[jqGrid] 컬럼 순서 변경시 오류가 날 경우 (0) | 2019.08.23 |
바벨(Babel)과 타입스크립트(Typescript)중 어떤걸 사용해야 할까 (0) | 2019.07.16 |