Reducer를 작성하면서 불변성을 지키기 위해 immer라는 라이브러리를 사용하였다.
평소에 코딩을 하면서 불변성을 지켜야지~ 지켜야지~ 달달 외우고 살긴 했지만 '왜? 안지키면 어떻게 되는데?' 라는 의문을 단 한번도 가져보지 않았다..🙄
immer를 사용한 김에 immer가 정확히 어떤 역할을 하는지, 불변성을 지켜야 하는 이유에 대해서 알아보자 🏃♂️
React에서 불변성을 유지하느라 복잡해진 코드를 짧고 간결하게 작성할 수 있도록 도와주는 라이브러리를 의미한다.
불변성이란 기존의 상태 값을 유지하면서 새로운 상태 값을 추가하는 것
을 의미한다.
React 에서는 얕은 비교를 통해 새로운 값인지 아닌지를 판단 한 후, 만약 새로운 값임을 판단하게 되면 부모 컴포넌트가 리렌더링을 하면 자식 컴포넌트도 함께 리렌더링 되게 된다.
state.push(10)
을 통해서 배열에 직접 10이라는 값을 추가한다.🍀이러한 이유 때문에 state 값을 변경하고 React에게 리렌더링을 원한다고 알리고 싶다면 새로운 배열을 생성해서 새로운 참조값을 생성하고 그 안에 기존의 값을 넣어줘야 한다!🍀
⚡ 얕은 비교란?
객체, 배열, 함수 같은 참조 타입들을 실제 내부 값까지 비교하지 않고 동일한 참조인지만을 비교하는 것을 의미!
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
];
const nextState = produce(baseState, draft => {
draft[1].done = true;
draft.push({title: "Tweet about it"});
});
immer에서는 produce
라는 함수를 사용하는데 첫번째 매개변수로는 수정하고 싶은 값(객체나 배열), 두번째 매개변수로는 첫번째 매개변수에 할당된 값을 바꾸는 함수이다.
실제로 Reducer에 적용한 코드도 한번 살펴보자!
immer 적용 전 코드
const initialState = {
name: '푸푸',
songs: { title: '느린여행' }
};
const reducer = (state = initialState, action) => {
switch (action.type){
case SET_USER:
return {
...state,
name:'김채원',
songs: {
...state.songs,
title:'내손을잡아'
}
};
default:
return state;
}
};
...state
를 통해 state를 펼쳐주고 ...state.songs
를 통해 state안의 songs 또한 펼쳐주고 값을 수정하게 된다. 만약 state가 더 많은 정보를 가지고 있다면 이 코드는.. 더 길어지고 가독성도 떨어지게 될 것이다.
immer 적용 후 코드
const initialState = {
name: '푸푸',
songs: { title: '느린여행' }
};
const reducer = (state = initialState, action) => {
return produce(state, (draft) => {
switch (action.type){
case SET_USER:
draft[0].name = action.data.name;
draft[0].songs.title = action.data.title;
break;
case ADD_USER:
draft.push({name: '김채원', songs: {title:'내손을잡아'}});
break;
default:
return draft;
}
});
};
draft[0].name = action.data.name
나 draft.push({name: '김채원', songs: {title:'내손을잡아'}})
를 통해 기존의 state를 복사할 필요 없이 값을 수정하고 새로운 값을 추가할 수 있다! (immer의 경우에는 각 case마다 끝에 break
를 꼭 붙여줘야한다!)