본문으로 바로가기

Redux 파일 구조

category React 2021. 2. 15. 02:09

기존의 Redux 파일 구조


리덕스 공식 문서에 있는 todo app의 파일 구조를 예시로 살펴보자

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6af90023-abb5-4de4-bdd5-00efa5acfe1c/Untitled.png

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 패턴을 사용할 때는 다음과 같은 규칙을 지켜야한다

  1. 항상 reducer() 라고 불리는 reducer함수를 export해야한다
  2. 항상 모듈의 action 객체 생성 함수를 export 해야한다
  3. action객체의 이름은 항상 /해당 reducer 이름/ACTION_TYPE 형태여야 한다
  4. 어쩌면 action 타입들을 UPPER_SNAKE_CASEexport 할 수 있다

위의 규칙을 따라서 아까 봤던 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