Skip to main content

리덕스 기초 2 (~Reducer)

리듀서(Reducer)란?#

리듀서는 스토어에 전송된 액션에 응답으로 어플리케이션의 상태가 어떻게 바뀌는지를 지정.

  • 즉, 실질적으로 변화를 일으키는 함수
function reducer(state, action){
// 상태 업데이트
return changedState;
}
// 또는
const reducer = (previousState, action) => changedState
  • 상태와 액션을 참고해서 새로운 상태를 반환함.

  • Array.prototype.reduce(reducer, ?initialValue)에 통과될 함수다.

  • 리듀서는 순수하게 유지되어야한다. 따라서 다음의 것들을 피하자.

    • 아규먼트 변이(mutate)
    • 사이드 이펙트 수행(API, routing transitions)
    • non-pure 함수 콜(Date.now(), Math.random())

공식 사이트 튜토리얼에서 다음과 같이 강조한다. Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

::: warning 순수 함수(pure function) 입력이 같다면 출력이 같아야하며, 사이드 이펙트가 없어야 한다. 다음의 미디엄에 설명이 잘되어있다. 순수함수란? 리덕스에서는 변화감지를 오브젝트의 주소로 하기 때문에, 인자로 받은 외부의 오브젝트의 프로퍼티를 바꾼다 한들 감지하지 못한다. 참고: 예시 설명이 잘되어있는 개인 블로그 :::

based on the todo#

State 형태 디자인#

리덕스에서는 모든 어플리케이션 state가 싱글 오브젝트 형태로 저장됨.

  • todo App을 위해서는 2가지를 저장해야한다.

    • 현재 선택된 비지빌리티(visibility, 가시) 필터.
    • 실제 todo의 리스트
  • state Tree에서 UI state와 데이터를 따로 보관할 것

{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}

::: warning 참고 state를 가능한 한 normalized하게(nesting 없이) 유지하라. 예를 들면 state 안에 todosById: { id -> todo }todos: array<id>을 넣는 것을 상용 앱에서는 더 나을 것이다. :::

액션 핸들링#

  • 변화감지 미적용 예
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state, action) {
if (typeof state === 'undefined') {
return initialState
}
// 여기선 어떠한 액션도 핸들하지 않는다.
// 우리에게 주어졌던 상태를 그냥 리턴한다.
return state
}

또는,

function todoApp(state = initialState, action) {
// 여기선 어떠한 액션도 핸들하지 않는다.
// 우리에게 주어졌던 상태를 그냥 리턴한다.
return state
}
  • 적용 예
import {
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
...
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return {...state, visibilityFilter:action.filter}
// Object.assign({}, state, {
// visibilityFilter: action.filter
//})
default:
return state
}
}
  1. state를 바꾸지 않는다.
  2. 이전 statedefault 케이스로 가져간다.

더 많이 액션 핸들링#

import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
...
function todoApp(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,
{
text: action.text,
completed: false
}
]
})
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
default:
return state
}
}

Source Code#

import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
  • reducer composition 라는 패턴을 사용했다. (기능이 쪼개진 부분)

  • combine reducer는 함수명을 갖고 아래 코드와 동일한 기능을 해주는 redux util 함수다.

export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}