이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
combineReducers 사용하기
핵심 개념
Redux 앱에서 가장 일반적인 state 형태는 각 최상위 키에 도메인별 데이터 "슬라이스"를 담고 있는 일반 JavaScript 객체입니다. 마찬가지로, 이러한 state 구조에 대한 리듀서 로직을 작성하는 가장 일반적인 접근법은 동일한 (state, action) 시그니처를 가진 "슬라이스 리듀서" 함수들을 사용하는 것입니다. 각 함수는 특정 state 슬라이스의 모든 업데이트를 관리합니다. 여러 슬라이스 리듀서가 동일한 액션에 응답하고 필요에 따라 자체 슬라이스를 독립적으로 업데이트한 후, 변경된 슬라이스들이 새로운 state 객체로 결합됩니다.
이 패턴이 매우 흔하기 때문에 Redux는 해당 동작을 구현하는 combineReducers 유틸리티를 제공합니다. 이는 _고차 리듀서(higher-order reducer)_의 예시로, 슬라이스 리듀서 함수들로 구성된 객체를 입력받아 새로운 리듀서 함수를 반환합니다.
combineReducers 사용 시 주의해야 할 몇 가지 중요한 개념:
-
무엇보다도
combineReducers는 Redux 리듀서 작성 시 가장 흔한 사례를 단순화하기 위한 유틸리티 함수일 뿐입니다. 애플리케이션에서 사용이 의무화된 것은 아니며, 모든 가능한 시나리오를 처리하지도 않습니다. 이를 사용하지 않고 리듀서 로직을 작성하는 것도 완전히 가능하며,combineReducer가 처리하지 못하는 경우 커스텀 리듀서 로직을 작성해야 하는 상황도 흔합니다. (예시와 권장사항은combineReducers의 한계를 넘어서 참조) -
Redux 자체는 state 구성 방식에 관여하지 않지만,
combineReducers는 사용자의 일반적 실수를 방지하기 위해 몇 가지 규칙을 강제합니다. (자세한 내용은combineReducers참조) -
자주 묻는 질문은 Redux가 액션 디스패치 시 "모든 리듀서를 호출하는가"입니다. 실제 루트 리듀서 함수는 하나뿐이므로 기본 답변은 "아니오"입니다. 그러나
combineReducers는 정확히 그런 방식으로 동작하는 특성을 가집니다. 새로운 state 트리를 구성하기 위해combineReducers는 각 슬라이스 리듀서를 현재 state 슬라이스와 액션으로 호출하여 응답 및 업데이트 기회를 제공합니다. 따라서 이 관점에서combineReducers사용은 "모든 리듀서를 호출"한다고 말할 수 있으며, 최소한 래핑된 모든 슬라이스 리듀서는 호출됩니다. -
루트 리듀서 생성뿐만 아니라 리듀서 구조의 모든 계층에서 사용 가능합니다. 다양한 위치에 여러 결합된 리듀서를 배치하고 이를 조합하여 루트 리듀서를 만드는 것이 매우 일반적입니다.
State 형태 정의
스토어 state의 초기 형태와 내용을 정의하는 두 가지 방법이 있습니다. 첫째, createStore 함수는 두 번째 인수로 preloadedState를 받을 수 있습니다. 이는 주로 브라우저의 localStorage처럼 다른 곳에 저장된 state로 스토어를 초기화하기 위함입니다. 다른 방법은 state 인수가 undefined일 때 루트 리듀서가 초기 state 값을 반환하는 것입니다. 이 두 접근법은 State 초기화에서 자세히 설명하지만, combineReducers 사용 시 추가 고려사항이 있습니다.
combineReducers는 슬라이스 리듀서 함수들로 구성된 객체를 입력받아 동일한 키를 가진 state 객체를 출력하는 함수를 생성합니다. 즉, createStore에 preloaded state가 제공되지 않으면 입력 슬라이스 리듀서 객체의 키 명명이 출력 state 객체의 키 명명을 결정합니다. 특히 기본 모듈 내보내기나 객체 리터럴 단축 표현 같은 기능 사용 시 이 이름 간 상관관계가 명확하지 않을 수 있습니다.
객체 리터럴 단축 표현과 combineReducers를 사용해 state 형태를 정의하는 예시:
// reducers.js
export default theDefaultReducer = (state = 0, action) => state
export const firstNamedReducer = (state = 1, action) => state
export const secondNamedReducer = (state = 2, action) => state
// rootReducer.js
import { combineReducers, createStore } from 'redux'
import theDefaultReducer, {
firstNamedReducer,
secondNamedReducer
} from './reducers'
// Use object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
})
const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
객체 리터럴 정의를 위한 단축 표현을 사용했기 때문에 결과 state의 키 이름이 임포트된 변수명과 동일합니다. 이는 항상 바람직한 동작이 아니며, 현대 JS 문법에 익숙하지 않은 개발자들에게 혼란을 주는 경우가 많습니다.
또한 결과 이름이 다소 어색합니다. 상태 키 이름에 'reducer' 같은 단어를 실제로 포함하는 것은 일반적으로 좋은 방법이 아닙니다. 키는 단순히 담고 있는 데이터의 도메인이나 유형을 반영해야 합니다. 이는 슬라이스 리듀서 객체에서 키 이름을 명시적으로 지정하여 출력 상태 객체의 키를 정의하거나, 객체 리터럴 단축 구문을 사용할 때 키를 설정하기 위해 가져온 슬라이스 리듀서의 변수 이름을 신중하게 변경해야 함을 의미합니다.
더 나은 사용법은 다음과 같을 수 있습니다:
import { combineReducers, createStore } from 'redux'
// Rename the default import to whatever name we want. We can also rename a named import.
import defaultState, {
firstNamedReducer,
secondNamedReducer as secondState
} from './reducers'
const rootReducer = combineReducers({
defaultState, // key name same as the carefully renamed default export
firstState: firstNamedReducer, // specific key name instead of the variable name
secondState // key name same as the carefully renamed named export
})
const reducerInitializedStore = createStore(rootReducer)
console.log(reducerInitializedStore.getState())
// {defaultState : 0, firstState : 1, secondState : 2}
이 상태 구조는 combineReducers에 전달한 키를 설정하는 데 주의를 기울였기 때문에 관련 데이터를 더 잘 반영합니다.