🐕 깊은 복사 구현하기 개 귀찮다.
리액트는 불변성을 유지해줘야 함과 동시에 객체의 중첩구조가 쉽게 만들어지는 모순을 갖고있습니다.
간편하게 쓸 수 있는 애들은 죄다 shallow copy이기 때문에
깊은 중첩 구조를 가진 애들에게는 얕은 복사를 여러번 해야하는 수고로움이 있어요
따라서 깊은 복사로 한번에 불변성을 유지해주면 참 편할 것입니다.
const object = {
somewhere: {
deep: {
inside: 3,
array: [1, 2, 3, 4],
},
},
};
이따구로 된 중첩구조에서 하나하나 스프레드 연산자 다 넣어주면 어지럽겠다 그죠
https://xionwcfm.tistory.com/189
근데 깊은 복사를 구현하는게 왜 귀찮은지는 제 예전 포스트에서 이유를 찾을 수 있습니다.
굳이 저거 만들어서 쓰시고 싶다면 말리진 않겠지만 그냥 갖다쓰는게 더 빠르긴 하겠네요
이번엔 갖다 써보는것을 한번 해보려고 합니다.
👻immer를 쓰고싶으면 immer를 깔아라...
yarn add immer
전 yarn을 쓰고 있으니 yarn add 명령어로 추가해줬습니다.
🥶 immer의 기본적인 사용법
let { produce } = require('immer');
console.log(produce);
let originalState = {
daeun: {
islegend: 'inziyong',
minjae: [1, 2, 3, 4, 5],
},
};
const nextState = produce(originalState, (draft) => {
originalState.daeun.minjae[0] = 500;
});
console.log(nextState);
console.log(originalState);
console.log(originalState.daeun.minjae === nextState.daeun.minjae);
처음엔 이렇게 코드를 짰는데 원본도 바뀌길래 뭐임? 하고 있다가
다시 사용법을 잘 살펴보니 원본 객체에 접근하는게 아니라
콜백의 인자로 받은 draft에 접근해야하는거였다.
let { produce } = require('immer');
console.log(produce);
// [Function (anonymous)]
let originalState = {
daeun: {
islegend: 'inziyong',
minjae: [1, 2, 3, 4, 5],
},
};
const nextState = produce(originalState, (draft) => {
draft.daeun.minjae[0] = 500;
});
console.log(nextState);
// { daeun: { islegend: 'inziyong', minjae: [ 500, 2, 3, 4, 5 ] } }
console.log(originalState);
//{ daeun: { islegend: 'inziyong', minjae: [ 1, 2, 3, 4, 5 ] } }
console.log(originalState.daeun.minjae === nextState.daeun.minjae);
//false
따라서 이렇게 콜백의 인자에 접근해서 값을 바꿔주면?
값이 아주 멋지게 바뀌는것을 볼 수 있다.
와 너무 편한데?
import 문으로 불러오고 싶었지만 아래와 같은 자잘한 오류가 발생해서
(node:4536) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
d:\REACT PROJECT\immer-tutorial\src\produce.js:1
import { produce } from 'immer';
^^^^^^
그냥 require문으로 불러와서 객체디스트럭처링 할당해서 써봤다.
저거 package.json 수정하는게 귀찮음
🌞진짜 실습
let { produce } = require('immer');
const originalState = [
{
id: 1,
todo: `전개연산자와 배열내장함수로 불변성 유지`,
checked: true,
},
{
id: 2,
todo: `immer로 유지`,
checked: false,
},
];
const nextState = produce(originalState, (draft) => {
const todo = draft.find((t) => t.id === 2);
todo.checked = true;
draft.push({
id: 3,
todo: '일정 관리 앱에 immer 적용하기',
checked: false,
});
draft.splice(
draft.findIndex((t) => t.id === 1),
1,
);
});
console.log(nextState);
화살표 함수에 유의하면서 작성합시다.
만약 중괄호를 축약하고 싶지않다면 명시적으로 리턴문을 작성해줄것..
const todo = draft.find((t) => {
return t.id === 2;
});
이렇게 말이에요
그리고 살짝 헷갈릴 수 있는 부분이 하나 더 있는데
draft.splice(
draft.findIndex((t) => t.id === 1),
1,
);
요부분은 splice 메서드를 지정된 인덱스만 제거하는 것이기때문에
splice 메서드 안에 findIndex 메서드가 첫번째 인자로 들어가는 거고
두번째 인자는 splice 메서드가 어느정도의 길이를 자를지를 정해주는 것...
[
{ id: 2, todo: 'immer로 유지', checked: true },
{ id: 3, todo: '일정 관리 앱에 immer 적용하기', checked: false }
]
잘 했으면 이렇게 값이 잘 출력되는 것을 확인할 수 있습니다.
😋이제 실제 프로젝트에 적용하기
간단하게 등록버튼을 누르면
아이디와 이름이 있는 리스트가 생성되고
리스트를 클릭하면 지워지는 프로그램을 만들것입니다.
import React, { useCallback, useState, useRef } from 'react';
import produce from 'immer';
import { Fragment } 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(
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 (
<>
<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>
</>
);
};
export default App;
에에.... 근데 책의 예제 코드가 좀 이상해요
전 위에서 분명 produce 함수의 첫번째 인자로는 베이스가 될 객체가 들어가야하고
두번째 인자로 함수를 넣어줘야한다고 배웠는데
책에서는 왜 냅다 첫번째 인자로 함수를 집어넣어버리는거죠?
const onChange = useCallback(
(e) => {
const { name, value } = e.target;
setForm(
produce(form, (draft) => {
draft[name] = value;
}),
);
},
[form],
);
이렇게 작성해야할 것 같은데
책에서는
setData(
produce(draft => {draft.array.push(info)})
)
이런식으로 냅다 집어 넣어 버려요
왜 그런가 했는데
immer에서 제공하는 produce 함수를 호출할 때 첫 번째 파라미터가 함수 형태라면
업데이트 함수를 반환한다고 하네요
let { produce } = require('immer');
const update = produce((draft) => {
draft.value = '난 업데이트';
});
const originalState = {
value: 1,
foo: 'bar',
};
const nextState = update(originalState);
console.log(nextState);
//{ value: '난 업데이트', foo: 'bar' }
와... 그러니까 produce 함수가 함수를 반환하게 만들어 놓고
그 함수를 setData 에 인자로 집어넣는 식으로 코드를 짰다?
무친...
🐶마치며
immer의 produce 함수 사용법도 간단한데
정말 편리하네요
이제 스프레드지옥에서 벗어날 수 있나봐요 너무 신나
'react' 카테고리의 다른 글
React.createPortal을 이용해 모달 만들기 (0) | 2023.05.15 |
---|---|
React.lazy와 코드스플리팅 (0) | 2023.04.18 |
React Router를 이용해보자 근데 실습을 곁들인 (0) | 2023.04.07 |
react-virtualized 를 사용한 렌더링 최적화를 해보자 (0) | 2023.04.06 |
리액트의 Context API를 알아보자.. (0) | 2023.04.04 |