8. 업데이터 함수를 만들어서 코드 리팩토링하기
우리가 기존에 만들었던 리듀서를 다시 한번 확인해봅시다:
[CHANGE_INPUT]: (state, action) => ({
...state,
input: action.payload,
}),
[CREATE]: (state, action) => ({
...state,
list: state.list.concat({
id: action.payload.id,
name: action.payload.text,
entered: false,
}),
}),
[ENTER]: (state, action) => ({
...state,
list: state.list.map(
item =>
item.id === action.payload
? { ...item, entered: !item.entered }
: item
),
}),
[LEAVE]: (state, action) => ({
...state,
list: state.list.filter(item => item.id !== action.payload),
}),
불변성을 유지하기 위해서 ... 을 정말 많이 사용했습니다. 많이 사용한다고해서 지금 이게 잘못된것은 아니지만, 가독성이 별로 좋지 않습니다. 추가적으로, 배열을 관리할때는 배열 내장함수들을 사용하고있는데 배열 원소를 업데이트 할 때나 삭제할 때 사용하는 코드들은 지금 이렇게 액션이 별로 없을땐 상관 없지만, 리듀서에서 여러종류의 배열을 다루게 되는 일이 생긴다면 가독성이 정말 좋지 않을 것입니다.
따라서, 우리가 업데이트 할 때마다 재사용되는 코드들을 함수화하여 더 높은 가독성을 보이는 코드로 리팩토링 하는 방법을 알아보겠습니다.
updaters 만들기
우선, store 디렉토리 안에 updaters.js 파일을 만드세요:
src/updaters.js
export function updateObject(state, updated) {
return {
...state,
...updated,
};
}
export function updateById(array, id, updater) {
return array.map(item => {
if (item.id !== id) return item;
return updater(item);
});
}
export function removeById(array, id) {
return array.filter(item => item.id !== id);
}
우리가 불변성을 관리하기 위하여 했던 작업들을 함수화하여 만들고 내보내주었습니다.
counter 리듀서 리팩토링
이제 우리가 만든 업데이터 함수들을 사용하여 counter 리듀서를 재작성해봅시다!
src/store/modules/counter.js
import { updateObject } from '../updaters';
// 액션 타입 정의
const CHANGE_COLOR = 'counter/CHANGE_COLOR';
const INCREMENT = 'counter/INCREMENT';
const DECREMENT = 'counter/DECREMENT';
// 액션 생섬함수 정의
export const changeColor = color => ({ type: CHANGE_COLOR, color });
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
// **** 초기상태 정의
const initialState = {
color: 'red',
number: 0,
};
// **** 리듀서 작성
export default function counter(state = initialState, action) {
switch (action.type) {
case CHANGE_COLOR:
return updateObject(state, { color: action.color });
case INCREMENT:
return updateObject(state, { number: state.number + 1 });
case DECREMENT:
return updateObject(state, { number: state.number - 1 });
default:
return state;
}
}
이 리듀서에서는 기존에 ... 로 처리하던것을 updateObject
를 사용하여 처리해주었습니다. 워낙 간단한 리듀서다보니 눈에 띄는 개선사항은 많이 없습니다.
waiting 리듀서 리팩토링
이제 waiting 리듀서를 리팩토링 해보겠습니다. 우리가 만든 updateById
와 removeById
가 배열 관리 로직을 더욱 보기 좋게 해줄 것입니다.
src/modules/waiting.js
import { createAction, handleActions } from 'redux-actions';
import { updateObject, updateById, removeById } from '../updaters';
const CHANGE_INPUT = 'waiting/CHANGE_INPUT'; // 인풋 값 변경
const CREATE = 'waiting/CREATE'; // 명단에 이름 추가
const ENTER = 'waiting/ENTER'; // 입장
const LEAVE = 'waiting/LEAVE'; // 나감
let id = 3;
// createAction 으로 액션 생성함수 정의
export const changeInput = createAction(CHANGE_INPUT, text => text);
export const create = createAction(CREATE, text => ({ text, id: id++ }));
export const enter = createAction(ENTER, id => id);
export const leave = createAction(LEAVE, id => id);
// 초기 상태 정의
const initialState = {
input: '',
list: [
{
id: 0,
name: '홍길동',
entered: true,
},
{
id: 1,
name: '콩쥐',
entered: false,
},
{
id: 2,
name: '팥쥐',
entered: false,
},
],
};
// handleActions 로 리듀서 함수 작성
export default handleActions(
{
[CHANGE_INPUT]: (state, action) =>
updateObject(state, { input: action.payload }),
[CREATE]: (state, action) =>
updateObject(state, {
list: state.list.concat({
id: action.payload.id,
name: action.payload.text,
entered: false,
}),
}),
[ENTER]: (state, action) =>
updateObject(state, {
list: updateById(state.list, action.payload, item =>
updateObject(item, { entered: !item.entered })
),
}),
[LEAVE]: (state, action) =>
updateObject(state, {
list: removeById(state.list, action.payload),
}),
},
initialState
);
이 리듀서에서는 ENTER 과 LEAVE 에서 업데이터 함수를 중첩해서 사용하기도 했습니다. 이제 코드를 봤을때 어떤 작업을 하고 있는지 한 눈에 알아보기 쉬워졌습니다!