기존의 Redux 파일 구조
리덕스 공식 문서에 있는 todo app의 파일 구조를 예시로 살펴보자
presentationer, container components 패턴을 활용하고 있는 component, contiainer 폴더를 제외하면
- actions
- constants
- reducers
3가지로 컴포넌트들을 분류한 걸 볼 수 있다
constants
constants 폴더에는 액션 객체를 생성하기 위한 상수들이 저장되어 있다
// in constants/ActionTypes.js
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL_TODOS = 'COMPLETE_ALL_TODOS'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
actions
actions 폴더의 index에서는 ActionTypes를 import해서 액션 객체 생성 함수를 관리하고 있다
// in actions/index.js
import * as types from '../constants/ActionTypes'
export const addTodo = text => ({ type: types.ADD_TODO, text })
export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })
export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
export const completeAllTodos = () => ({ type: types.COMPLETE_ALL_TODOS })
export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })
export const setVisibilityFilter = filter => ({ type: types.SET_VISIBILITY_FILTER, filter})
reducers
reducers 폴더의 index에서는 액션에 따라서 어떻게 동작할지가 구현되어있다
//in reducers/index.js
import {
ADD_TODO,
DELETE_TODO,
EDIT_TODO,
COMPLETE_TODO,
COMPLETE_ALL_TODOS,
CLEAR_COMPLETED
} from '../constants/ActionTypes'
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
}
]
case DELETE_TODO:
return state.filter(todo =>
todo.id !== action.id
)
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, text: action.text } :
todo
)
case COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, completed: !todo.completed } :
todo
)
case COMPLETE_ALL_TODOS:
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
case CLEAR_COMPLETED:
return state.filter(todo => todo.completed === false)
default:
return state
}
}
불편함
이러한 구조로 redux를 사용하다 보면 기능을 추가하거나 수정할 일이 생겼을 때 3가지 파일을 모두 건드려야 하는 상황이 생기는데
이러한 불편함을 개선하고자 해서 나온 것이 바로 ducks 패턴이다
Ducks 패턴
리덕스를 사용할 때 기능별로 하나의 파일에 액션 객체 생성자, 액션 상수, 리듀스 함수 관련 코드를 작성하고 모듈 폴더에서 관리하는 파일 구조를 말한다
규칙
Ducks 패턴을 사용할 때는 다음과 같은 규칙을 지켜야한다
- 항상
reducer()
라고 불리는 reducer함수를 export해야한다 - 항상 모듈의 action 객체 생성 함수를
export
해야한다 - action객체의 이름은 항상
/해당 reducer 이름/ACTION_TYPE
형태여야 한다 - 어쩌면 action 타입들을
UPPER_SNAKE_CASE
로export
할 수 있다
위의 규칙을 따라서 아까 봤던 todo 예제를 하나의 module로 만들면 다음과 같다
// modules/todos.js
const ADD_TODO = 'todos/ADD_TODO'
const DELETE_TODO = 'todos/DELETE_TODO'
const EDIT_TODO = 'todos/EDIT_TODO'
const COMPLETE_TODO = 'todos/COMPLETE_TODO'
const COMPLETE_ALL_TODOS = 'todos/COMPLETE_ALL_TODOS'
const CLEAR_COMPLETED = 'todos/CLEAR_COMPLETED'
const SET_VISIBILITY_FILTER = 'todos/SET_VISIBILITY_FILTER'
export const addTodo = text => ({ type: types.ADD_TODO, text })
export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })
export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
export const completeAllTodos = () => ({ type: types.COMPLETE_ALL_TODOS })
export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })
export const setVisibilityFilter = filter => ({ type: types.SET_VISIBILITY_FILTER, filter})
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
}
]
case DELETE_TODO:
return state.filter(todo =>
todo.id !== action.id
)
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, text: action.text } :
todo
)
case COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, completed: !todo.completed } :
todo
)
case COMPLETE_ALL_TODOS:
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
case CLEAR_COMPLETED:
return state.filter(todo => todo.completed === false)
default:
return state
}
}
책에서 리덕스를 활용한 상태 관리 예제를 먼저 하고 Ducks 패턴을 더 알아봤는데
예제에서 1번 규칙은 지키지 않고 있던 걸 보면 반드시 지킬 필요는 없는 것 같다
'React' 카테고리의 다른 글
Redux-saga (0) | 2021.02.25 |
---|---|
리덕스-미들웨어 만들기 (0) | 2021.02.16 |
프레젠테이셔널, 컨테이너 컴포넌트 패턴 (0) | 2021.02.13 |
불변성 (0) | 2021.02.02 |
Effect Hook (0) | 2020.11.18 |