combineReducers

앱이 점점 복잡해지면 리듀싱 함수상태의 독립된 부분들을 관리하는 함수들로 분리하고 싶어질겁니다.

combineReducers 헬퍼 함수는 서로 다른 리듀싱 함수들을 값으로 가지는 객체를 받아서 createStore에 넘길 수 있는 하나의 리듀싱 함수로 바꿔줍니다.

생성된 리듀서는 내부의 모든 리듀서들을 호출하고 결과를 모아서 하나의 상태 객체로 바꿔줍니다. 상태 객체의 형태는 reducers로 전달된 키들을 따릅니다.

결과적으로 상태 객체는 이런 형태가 됩니다:

{
reducer1: ...
reducer2: ...
}

상태의 키 이름은 전달되는 객체의 키 이름을 다르게 함으로써 제어할 수 있습니다. 예를 들어, 상태의 모양이 { todos, counter }가 되게하기 위해서는 combineReducers({ todos: myTodosReducer, counter: myCounterReducer })이라고 호출하면 됩니다.

관행적으로는 관리하는 상태 조각의 이름을 따라 리듀서를 명명하는 것입니다. 이렇게 하면 ES6의 속성 단축 표기를 사용할 수 있습니다: combineReducers({ counter, todos }). 이는 combineReducers({ counter: counter, todos: todos })라고 쓰는 것과 동일합니다.

Flux 사용자를 위한 한마디

이 함수는 Flux가 서로 다른 상태를 관리하기 위해 서로 다른 스토어를 두는 것 처럼 리듀서가 각자의 상태 조각들을 관리하게 도와줍니다. Redux에는 하나의 스토어 뿐이지만, combineReducers를 통해 리듀서들을 동일하게 논리적으로 분리할 수 있습니다.

인수

  1. reducers (Object): 하나로 합쳐질 각각의 리듀싱 함수들을 값으로 가지는 객체입니다. 각 함수들이 따라야 하는 규칙은 아래의 주석을 참고하세요.

예전의 문서는 리듀서 객체를 얻어오기 위해 ES6의 import * as reducers 문법을 쓰도록 제안했습니다. 이 방식이 많은 혼란을 가져왔기 때문에 이제는 reducers/index.js에서 combineReducers()를 통해 하나의 리듀서를 익스포트할 것을 권장합니다. 예시가 아래에 있습니다.

반환

(Function): reducers 객체 안의 모든 리듀서들을 실행해서 하나의 상태 객체를 만드는 리듀서입니다.

주석

이 함수는 초보자들이 쉽게 빠지는 실수를 방지하기 위해 약간은 완고하고 편향되어 있습니다. 여러분이 직접 루트 리듀서를 작성할 때는 따를 필요가 없었던 규칙들을 강요하는 것은 이 때문입니다.

combineReducers로 전달되는 모든 리듀서는 아래의 규칙을 따라야만 합니다:

  • 식별되지 않은 모든 상태에 대해서는 첫 인수로 주어진 state를 그대로 반환해야 합니다.

  • undefined를 반환해서는 안 됩니다. 이른 return문에서 쉽게 할 수 있는 실수로, 다른 곳에서 에러가 나기 전에 combineReducers에서 에러를 발생(throw)시킵니다.

  • stateundefined로 주어지면 반드시 해당 리듀서의 초기 상태를 반환해야 합니다. 예전 룰을 따르면 초기 상태 또한 undefined가 될 수 없습니다. ES6의 선택적 인수(optional arguments) 문법을 사용하면 간편하지만, 직접 첫 인수가 undefined가 아닌지 확인할 수도 있습니다.

combineReducers가 이들 규칙을 지키는지 확인하지만, 여러분이 기억하고 지키는 것도 중요합니다. combineReducers는 여러분이 Redux.createStore(combineReducers(...), initialState)를 사용해서 초기 상태를 명시하더라도, 리듀서에 undefined를 전달해서 직접 확인합니다. 그러므로 여러분이 undefined를 받을 의도 없이 코드를 작성했더라도, 반드시 리듀서가 undefined를 상태로 받더라도 제대로 작동하는지 확인하세요.

예시

reducers/todos.js

export default function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ])
default:
return state
}
}

reducers/counter.js

export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

reducers/index.js

import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})

App.js

import { createStore } from 'redux'
import reducer from './reducers/index'
let store = createStore(reducer)
console.log(store.getState())
// {
// counter: 0,
// todos: []
// }
store.dispatch({
type: 'ADD_TODO',
text: 'Use Redux'
})
console.log(store.getState())
// {
// counter: 0,
// todos: [ 'Use Redux' ]
// }

  • 이 헬퍼는 편의를 위한 것일 뿐입니다! 다르게 작동하는 여러분만의 combineReducers를 직접 작성할 수도 있고, 자식 리듀서들에서 상태 객체를 조합하는 루트 리듀싱 함수를 명시적으로 만들 수도 있습니다.

  • combineReducers는 리듀서 계층의 최상위 뿐 아니라 어느 단계에서도 호출할 수 있습니다. 자식 리듀서가 너무 복잡해지면 다시 사용해서 손자 리듀서를 만들고 이런 식으로 반복할 수도 있습니다.