[ReactJS] 리덕스 모듈 쪼개기

  1. 액션 타입 2. 액션 생성함수 3. 리듀서 가 들어있는 자바스크립트 파일

리덕스 모듈 만들기

modules라는 디렉토리 안에 counter / todo 모듈 파일을 생성하기.

modules
└ counter.js
└ todo.js

modules/counter.js :

// counter 모듈

// 1. 액션 타입 정의하기
// 액션 이름 중복 방지를 위해서 앞에 'counter/'안에 속해있다는 접두사를 붙임
const SET_DIFF = 'counter/SET_DIFF'
const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'

// 2. 액션 함수 생성
export const setDIFF = diff => ({
  type: SET_DIFF,
  diff,
})
export const increase = () => ({
  type: INCREASE,
})
export const decrease = () => ({
  type: DECREASE,
})

// 3. 초기 상태를 정의
const initialState = {
  number: 0,
  diff: 1,
}

// 4. 리듀서 선언
// 리듀서는 export default 로 반드시 내보내줘야함.
// 모듈을 가져다 써야하기 때문임
export default function counter(state = initialState, action) {
  switch (action.type) {
    case SET_DIFF:
      return {
        ...state,
        diff: action.diff,
      }
    case INCREASE:
      return {
        ...state,
        number: state.number + state.diff,
      }
    case DECREASE:
      return {
        ...state,
        number: state.number - state.diff,
      }
    default:
      return state
  }
}

modules/todo.js :

// 1. 액션 타입 정의
const ADD_TODO = 'todos/ADD_TODO'
const TOGGLE_TODO = 'todos/TOGGLE_TODO'

// todo 리스트의 고유 idx 설정
let idx = 1

// 2. 액션 함수 생성
export const addTodo = text => ({
  type: ADD_TODO,
  todo: {
    idx: idx++,
    text,
  },
})

export const toggleTodo = idx => ({
  type: TOGGLE_TODO,
  idx,
})

// 3. 초깃값 정의
// idx값과 text, 완료여부 객체를 저장
const initialState = []

// 4. 리듀서 내보내기
export default function todos(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return state.concat(action.todo)
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.idx === action.idx ? { ...todo, done: !todo.done } : todo
      )
    default:
      return state
  }
}

루트 리듀서

생성한 리덕스 모듈은 두개. 따라서 리듀서 또한 두개가 내보내짐. 한 프로젝트 내 리듀서가 여러개일 경우, combinReducers라는 함수를 통해 하나의 리듀서로 통합한다. 이걸 루트 리듀서라고 함!

modules/index.js :

import { combineReducers } from 'redux'
import counter from './counter'
import todo from './todo'

const rootReducer = combineReducers({
  counter,
  todo,
})

export default rootReducer

스토어 생성하기

index.js에서 스토어를 생성해서 루트 리듀서를 넣어줌. _index.js : _

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
// 스토어 생성 함수 불러오기
import { createStore } from 'redux'
// 루트 리듀서 불러오기
import rootReducer from './module'

ReactDOM.render(<App />, document.getElementById('root'))

const store = createStore(rootReducer)

serviceWorker.unregister()

리액트 리덕스 적용하기

react-redux 라이브러리를 설치하여 리액트 내에서 리덕스를 사용해보자.

index.js에서 Provider 라는 컴포넌트를 불러와서 App컴포넌트를 감싸 props로 store를 넣어줌.

index.js :

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
import { createStore } from 'redux'
import rootReducer from './module'

// Provider 컴포넌트 불러오기
import { Provider } from 'react-redux'

const store = createStore(rootReducer)

// App 컴포넌트를 Provider 컴포넌트로 감싼 후,
// store 를 props로 넘겨준다.
// 이렇게 하면 전역에서 store를 공유하며 어디서든 접근이 가능함.
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

serviceWorker.unregister()

프리젠테이셔널 컴포넌트 구현

props로 필요한 값을 받아만 와서 UI를 구현하는 컴포넌트. component 디렉토리 내, Counter.js 파일을 생성하여 컴포넌트를 만들어준다.

component/Counter.js :

import React from 'react'
// Counter 컴포넌트는 only 읽기 전용, UI 관련 컴포넌트
// 스토어에 직접 접근 X / props를 통해 값을 받기만 한다.

// 전역에서 store에 접근할 수 있음.
function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
  const onChange = e => {
    // e.target.value는 문자열이므로,
    // int 값으로 변환해준다.
    onSetDiff(parseInt(e.target.value, 10))
  }

  return (
    <div>
      <h1>{number}</h1>
      <div>
        <input type="number" value={diff} min="1" onChange={onChange} />
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
      </div>
    </div>
  )
}

export default Counter

컨테이너 컴포넌트 구현

스토어의 상태 조회 or 액션을 디스패치할 수 있는 컴포넌트를 의미함. 다른 프리젠테이셔널 컴포넌트를 불러와서 사용함.

CounterContainer.js :

import React from 'react'
// useSelector = 스토어 상태를 조회하는 리액트 hook
// = getState()
import { useSelector, userDispatch } from 'react-redux'
import Counter from '../components/Counter'
import { increase, decrease, setDiff } from '../module/counter'

function CounterContainer() {
  const { number, diff } = useSelector(state => ({
    number: state.counter.number,
    diff: state.counter.diff,
  }))

  // useDispatch, 스토어의 dispatch를 함수에서 사용하게 하는 hook임.
  const dispatch = userDispatch()
  // 각 액션들을 디스패치하는 함수 생성
  // 액션 함수를 생성하는 함수를 파라미터값으로 넣어줌
  const onIncrease = () => dispatch(increase())
  const onDecrease = () => dispatch(decrease())
  const onSetDiff = () => dispatch(setDiff(diff))

  // 프리젠테이셔널 컴포넌트를 불러와 컴포넌트 안에 props값을 세팅
  return (
    <Counter
      number={number}
      diff={diff}
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onSetDiff={onSetDiff}
    />
  )
}

export default CounterContainer

컨테이너 컴포넌트는 App 컴포넌트에서 불러와 렌더링 하면 됨.

App.js :

import React from 'react'
import CounterContainer from './containers/CounterContainer'

function App() {
  return (
    <div>
      <CounterContainer />
    </div>
  )
}

export default App