리액트 최적화(3) immer 불변성 간소화
immer 란?
❗immer(이머)
💡 JavaScript에서 불변성을 유지하면서 상태를 간편하게 업데이트할 수 있도록 도와주는 라이브러리.
💡 immer는 복잡한 중첩된 객체나 배열을 다룰 때 코드를 더 읽기 쉽고 유지보수하기 쉽게 만든다.
불변성 재학습
예제1 (불변성 지키기 기본)
const object = {
a: 1,
b: 2
};
object.b = 3;
⚠️ 이렇게 직접 수정하면 안되고 !
const object = {
a: 1,
b: 2
};
const nextObject = {
...object,
b: 3
};
💡 이렇게 얕은복사로 사용해서 불면성을 지키며 새로운 객체를 만들고 수정해야한다.
예제2 (불변성 지키기 배열)
const todos = [
{
id: 1,
text: '할 일 #1',
done: true
}}
];
const inserted = todos.concat({
id: 3,
text: '할 일 #3',
done: false
});
💡 배열도 concat 을 사용해서 불면성을 지키며 새로운 객체를 만들고 수정해야한다.
💡 push, splice 등의 함수를 사용하거나 n 번째 항목을 직접 수정하면 안된다.
예제2 (불변성 지키기 배열 심화)
const state = {
posts: [
{
id: 1,
title: '제목입니다.',
body: '내용입니다.',
comments: [
{
id: 1,
text: '와 정말 잘 읽었습니다.'
}
]
},
{
id: 2,
title: '제목입니다.',
body: '내용입니다.',
comments: [
{
id: 2,
text: '또 다른 댓글 어쩌고 저쩌고'
}
]
}
],
selectedId: 1
};
💡 위 코드에서 posts 배열 안의 id 가 1 인 post 객체를 찾아서, comments 에 새로운 댓글 객체를 추가해줘야 한다면?
// 방법1
const nextState = {
...state,
posts: state.posts.map(post =>
post.id === 1
? {
...post,
comments: post.comments.concat({ id: 2, text: '새로운 댓글' })
}
: post
)
};
// 방법2
const info = {
id: 2,
text: '새로운 댓글'
};
const nextState = {
...state,
posts: state.posts.map(post =>
post.id === 1
? {
...post,
comments: post.comments.concat(info)
}
: post
)
};
💡 위 처럼 불변성 하나 지키는데 진짜 복잡하다.
💡이럴 때, immer 라는 라이브러리를 사용하면 엄청 편하게 불변성 조작이 가능하다.
yarn add immer 설치
yarn add immer
import produce from 'immer';
💡코드의 상단에서 immer 를 위 처럼 사용한다.
💡보통 produce 라는 이름으로 불러온다.
Immer(param1:상태, param2:함수) 사용법(1)
❗immer (param1:obj, param2:func) 함수
- 💡param1: 수정하고 싶은 상태
- 💡param2: 어떻게 업데이트하고 싶을지 정의하는 함수를 넣는다.
- 💡불변성 신경쓰지 않고 업데이트 가능하다. 끝.
예시 코드
import produce from "immer";
const originalState = [
{
id: 1,
todo: "전개 연산자와 배열 내장 함수로 분변성 유지하기",
chekcked: 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
);
});
Immer(param1:함수) 사용법(2)
❗immer (param1:func) 함수
- 💡param1: 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환.
예시 코드
import produce from "immer";
const state = {
number: 1,
dontChangeMe: 2
};
const nextState = produce(state, draft => {
draft.dontChangeMe += 1;
});
console.log(nextState);
onChange() 함수
/* AS-IS */
const onChange = useCallback(
e => {
const { name, value } = e.target;
setForm({
...form,
[name]: [value]
});
},
[form]
);
/* TO-BE */
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(
produce(draft => {
draft[name] = value;
})
);
}, []);
onSubmit() 함수
/* AS-IS */
// form 등록을 위한 함수
const onSubmit = useCallback(
e => {
e.preventDefault();
// 갱신할 값
const info = {
id: nextId.current += 1,
name: form.name,
username: form.username
};
// array 에 새 항목 등록
setData({
...data,
array: data.array.concat(info)
/*
// 이렇게도 가능 !
array: data.array.concat({
id: nextId.current += 1,
name: form.name,
username: form.username
})
*/
});
// form 초기화
setForm({
name: '',
username: ''
});
}, [data, form.name, form.username]
);
/* TO-BE */
const onSubmit = useCallback(
e => {
e.preventDefault();
// 갱신할 값
const info = {
id: nextId.current += 1,
name: form.name,
username: form.username
};
// array 에 새 항목 등록
setData(
produce(draft => {
draft.array.push(info);
})
);
// form 초기화
setForm({
name: '',
username: ''
});
},
[form.name, form.username]
);
onRemove() 함수
/* AS-IS */
const onRemove = useCallback(
id => {
setData({
...data,
array: data.array.filter(info => info.id !id)
});
}, [data]
);
/* TO-BE */
const onRemove = useCallback(
id => {
setData(
produce(draft => {
draft.array.splice(draft.array.findIndex(info => info.id === id), 1);
})
);
},
[]
);
❗splice()
💡 시작인덱스 부터 ~ 지정 갯수만큼 제거하고 새로운 배열로 반환
- param1: 시작 인덱스
- param2: 제거할 요소의 개수
❗예제
const fruits = ['apple', 'banana', 'orange', 'grape', 'kiwi']; const removedFruits = fruits.splice(1, 2);
// 수행 후 console.log(fruits); 결과: ['apple', 'grape', 'kiwi'] console.log(removedFruits); 결과: ['banana', 'orange']
immer 사용시 주의
❗무조건 사용은 지양한다.
💡 immer를 사용한다고 해서 무조건 코드가 간결한건 아니다.
💡 onRemove의 경우 배열 내장 함수 filter를 사용하는 것이 코드가 더 깔끔하므로 , 굳이 immer를 적용할 필요❌
💡 immer는 불변성을 유지하는 코드가 복잡할 때만 사용해도 충분하다.
Leave a comment