3-3-리듀서-리팩토링하기
유틸리티 함수 추출
케이스 리듀서 추출
데이터 관리를 도메인으로 분리하기
보일러 플레이트 줄이기
슬라이스로 리듀서 결합하기
초기 리듀서
const initialState = {
visibilityFilter : 'SHOW_ALL',
todos : []
};
function appReducer(state = initialState, action) {
switch(action.type) {
case 'SET_VISIBILITY_FILTER' : {
return Object.assign({}, state, {
visibilityFilter : action.filter
});
}
case 'ADD_TODO' : {
return Object.assign({}, state, {
todos : state.todos.concat({
id: action.id,
text: action.text,
completed: false
})
});
}
case 'TOGGLE_TODO' : {
return Object.assign({}, state, {
todos : state.todos.map(todo => {
if (todo.id !== action.id) {
return todo;
}
return Object.assign({}, todo, {
completed : !todo.completed
})
})
});
}
case 'EDIT_TODO' : {
return Object.assign({}, state, {
todos : state.todos.map(todo => {
if (todo.id !== action.id) {
return todo;
}
return Object.assign({}, todo, {
text : action.text
})
})
});
}
default : return state;
}
}
유틸리티 함수 추출하기
위 리듀서에서 배열의 특정 항목을 업데이트하는 패턴이 발견할 수 있다. 업데이트된 필드를 포함하여 새로운 객체를 반환하는 함수를 만들어보자.
function updateObject(oldObject, newValues) {
// 새로운 객체를 첫번째 매개변수로 전달한다는 아이디어를 캡슐화
// 데이터를 변경하는 대신에 데이터를 확실히 복사하기 위해 Object.assign을 사용합니다.
return Object.assign({}, oldObject, newValues);
}
function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if(item.id !== itemId) {
// 한가지 항목만 업데이트하기 때문에 다른 항목은 유지합니다.
return item;
}
// 업데이트 된 항목을 만들기 위해 주어진 콜백을 사용합니다.
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
}
updateItemInArray의 updateItemCallback으로 업데이트 방식 정하는거 좋은듯
케이스 리듀서 추출
액션 별로 리듀서 동작을 함수로 정의하여 호출. 무슨일 일어나는지 명확해지고, 어떤 패턴이 보이기 시작할거다?
function appReducer(state = initialState, action) {
switch(action.type) {
case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(state, action);
case 'ADD_TODO' : return addTodo(state, action);
case 'TOGGLE_TODO' : return toggleTodo(state, action);
case 'EDIT_TODO' : return editTodo(state, action);
default : return state;
}
}
케이스 리듀서 추출
각 케이스를 자체함수로 나눌 수 있다.
function setVisibilityFilter(state, action) {
return updateObject(state, {visibilityFilter : action.filter });
}
function addTodo(state, action) {
const newTodos = state.todos.concat({
id: action.id,
text: action.text,
completed: false
});
return updateObject(state, {todos : newTodos});
}
function toggleTodo(state, action) {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, {completed : !todo.completed});
});
return updateObject(state, {todos : newTodos});
}
function editTodo(state, action) {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, {text : action.text});
});
return updateObject(state, {todos : newTodos});
}
function appReducer(state = initialState, action) {
switch(action.type) {
case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(state, action);
case 'ADD_TODO' : return addTodo(state, action);
case 'TOGGLE_TODO' : return toggleTodo(state, action);
case 'EDIT_TODO' : return editTodo(state, action);
default : return state;
}
}
각 액션에 대해 무슨 일이 일어나는지 매우 명확하다. 또한, 어떤 패턴이 나타나기 시작함을 볼 수 있다.
데이터 관리 도메인으로 분리하기
필터 로직과 todo로직을 분리시키자.
function visibilityReducer(visibilityState = 'SHOW_ALL', action) {
switch(action.type) {
case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(visibilityState, action);
default : return visibilityState;
}
};
function todosReducer(todosState = [], action) {
switch(action.type) {
case 'ADD_TODO' : return addTodo(todosState, action);
case 'TOGGLE_TODO' : return toggleTodo(todosState, action);
case 'EDIT_TODO' : return editTodo(todosState, action);
default : return todosState;
}
}
function appReducer(state = initialState, action) {
return {
todos : todosReducer(state.todos, action),
visibilityFilter : visibilityReducer(state.visibilityFilter, action)
};
}
보일러 플레이트 줄이기
switch문을 케이스 함수를 위한 ActionType의 룩업테이블 만들기. createReducer
를 통해 보일러 플레이트를 줄여보자.
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
const visibilityReducer = createReducer('SHOW_ALL', {
'SET_VISIBILITY_FILTER' : setVisibilityFilter
});
const todosReducer = createReducer([], {
'ADD_TODO' : addTodo,
'TOGGLE_TODO' : toggleTodo,
'EDIT_TODO' : editTodo
});
슬라이스로 리듀서 결합하기
최상위 앱 리듀서에 대한 "상태의 조각"의 로직을 처리하기 위해 combineReducers
유틸을 사용할 수 있다.
// "루트 리듀서"
const appReducer = combineReducers({
visibilityFilter : visibilityReducer,
todos : todosReducer
});
앞에서 설명한 리듀서 종류를 참고하여 전체코드를 다시 보자
// 재사용 가능한 유틸리티 함수
function updateObject(oldObject, newValues) {
// 새로운 객체를 첫번째 매개변수로 전달한다는 아이디어를 캡슐화
// 데이터를 변경하는 대신에 데이터를 확실히 복사하기 위해 Object.assign을 사용합니다.
return Object.assign({}, oldObject, newValues);
}
function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if(item.id !== itemId) {
// 한가지 항목만 업데이트하기 때문에 다른 항목은 유지합니다.
return item;
}
// 업데이트 된 항목을 만들기 위해 주어진 콜백을 사용합니다.
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
}
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
// 특정 케이스의 핸들러 ("케이스 리듀서")
function setVisibilityFilter(visibilityState, action) {
// 기술적으로, 우리는 이전의 상태에 대해서도 신경쓰지 않습니다.
return action.filter;
}
// 전체 상태의 조각에 대한 핸들러("슬라이스 리듀서")
const visibilityReducer = createReducer('SHOW_ALL', {
'SET_VISIBILITY_FILTER' : setVisibilityFilter
});
// 케이스 리듀서
function addTodo(todosState, action) {
const newTodos = todosState.concat({
id: action.id,
text: action.text,
completed: false
});
return newTodos;
}
// 케이스 리듀서
function toggleTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, {completed : !todo.completed});
});
return newTodos;
}
// 케이스 리듀서
function editTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, {text : action.text});
});
return newTodos;
}
// 슬라이스 리듀서
const todosReducer = createReducer([], {
'ADD_TODO' : addTodo,
'TOGGLE_TODO' : toggleTodo,
'EDIT_TODO' : editTodo
});
// "루트 리듀서"
const appReducer = combineReducers({
visibilityFilter : visibilityReducer,
todos : todosReducer
});
helperUtility
updateObject
createReducer
케이스 리듀서: case에 대한 handler
setVisibilityFilter
addTodo
슬라이스 리듀서: 전체 상태의 조각에 대한 핸들러
visibilityReducer
todosReducer
루트 리듀서
appReducer
위와 같은 리팩토링을 통해, 각 함수의 책임이 줄었고 의도가 명확해졌다. 실제로 작성하게되면 reducerUtilities.js
, visibilityReducer.js
, todosReducer.js
, rootReducer.js
와 같이 파일로 분리될거다.
Last updated
Was this helpful?