이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
상태 초기화
애플리케이션 상태를 초기화하는 두 가지 주요 방법이 있습니다. createStore 메서드는 두 번째 인자로 선택적 preloadedState 값을 받을 수 있습니다. 리듀서는 또한 undefined인 인입 상태 인자를 확인하고 기본값으로 사용할 값을 반환함으로써 초기값을 지정할 수 있습니다. 이는 리듀서 내부에서 명시적 검사를 통해 수행하거나 기본 인자 값 구문(function myReducer(state = someDefaultValue, action))을 사용해 구현할 수 있습니다.
이 두 접근 방식이 어떻게 상호작용하는지 항상 명확하지 않을 수 있습니다. 다행히 이 프로세스는 예측 가능한 규칙을 따릅니다. 각 요소가 어떻게 조화를 이루는지 살펴보겠습니다.
요약
combineReducers() 또는 유사한 수동 코드 없이, preloadedState는 항상 리듀서의 state = ...보다 우선 적용됩니다. 리듀서에 전달된 state가 preloadedState이며 undefined가 아니므로 인자 구문이 적용되지 않기 때문입니다.
combineReducers()를 사용할 때는 동작이 더 미묘합니다. preloadedState에 지정된 상태를 가진 리듀서는 해당 상태를 받습니다. 다른 리듀서들은 undefined를 받게 되며, 이 때문에 지정한 state = ... 기본 인자로 대체됩니다.
일반적으로 preloadedState는 리듀서가 지정한 상태보다 우선합니다. 이는 리듀서가 자신에게 적합한 초기 데이터를 기본 인자로 지정할 수 있게 하면서도, 영구 저장소나 서버에서 스토어를 복원할 때 기존 데이터(전체 또는 일부)를 로드할 수 있도록 합니다.
참고: preloadedState를 사용해 초기 상태가 채워진 리듀서도 state가 undefined로 전달될 때 처리할 기본값을 제공해야 합니다. 모든 리듀서는 초기화 시 undefined를 전달받으므로, undefined가 주어졌을 때 어떤 값을 반환하도록 작성되어야 합니다. 이 값은 undefined가 아닌 어떤 값이든 가능합니다. 여기서 preloadedState 섹션을 중복으로 작성할 필요는 없습니다.
상세 설명
단일 리듀서
먼저 단일 리듀서가 있는 경우를 생각해 봅시다. combineReducers()를 사용하지 않는 경우입니다.
리듀서는 다음과 같을 수 있습니다:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
이제 이 리듀서로 스토어를 생성합니다.
import { createStore } from 'redux'
const store = createStore(counter)
console.log(store.getState()) // 0
초기 상태가 0인 이유는 무엇일까요? createStore의 두 번째 인자가 undefined였기 때문입니다. 이 값이 첫 번째 호출 시 리듀서에 전달된 state입니다. Redux가 초기화될 때 상태를 채우기 위해 "더미" 액션을 디스패치합니다. 따라서 counter 리듀서는 state가 undefined인 상태로 호출됩니다. 이것이 바로 기본 인자가 "활성화"되는 경우입니다. 결과적으로, 기본 state 값(state = 0)에 따라 state는 이제 0이 됩니다. 이 상태(0)가 반환됩니다.
다른 시나리오를 살펴보겠습니다:
import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42
이번에는 왜 0이 아니라 42일까요? createStore가 두 번째 인자로 42를 받았기 때문입니다. 이 인자는 더미 액션과 함께 리듀서에 전달되는 state가 됩니다. 이번에는 state가 undefined가 아니므로(값이 42입니다!) 기본 인자 구문이 적용되지 않습니다. state는 42이며, 리듀서는 42를 반환합니다.
결합된 리듀서
이제 combineReducers()를 사용하는 경우를 살펴보겠습니다. 두 개의 리듀서가 있습니다:
function a(state = 'lol', action) {
return state
}
function b(state = 'wat', action) {
return state
}
combineReducers({ a, b })로 생성된 리듀서는 다음과 같습니다:
// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}
preloadedState 없이 createStore를 호출하면 state가 {}로 초기화됩니다. 따라서 a와 b 리듀서가 호출될 때 state.a와 state.b는 undefined가 됩니다. a와 b 리듀서 모두 state 인자로 undefined를 받으며, 기본 state 값을 지정했다면 해당 값이 반환됩니다. 이렇게 결합된 리듀서는 첫 호출에서 { a: 'lol', b: 'wat' } 상태 객체를 반환합니다.
import { createStore } from 'redux'
const store = createStore(combined)
console.log(store.getState()) // { a: 'lol', b: 'wat' }
다른 시나리오를 살펴보겠습니다:
import { createStore } from 'redux'
const store = createStore(combined, { a: 'horse' })
console.log(store.getState()) // { a: 'horse', b: 'wat' }
이제 createStore() 인자로 preloadedState를 지정했습니다. 결합된 리듀서가 반환하는 상태는 a 리듀서를 위해 지정한 초기 상태와 b 리듀서가 자체적으로 선택한 'wat' 기본 인자가 조합된 결과입니다.
결합된 리듀서의 동작을 다시 떠올려 봅시다:
// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}
이 경우 state가 지정되었으므로 {}로 대체되지 않습니다. state는 a 필드가 'horse'이지만 b 필드는 없는 객체입니다. 따라서 a 리듀서는 state로 'horse'를 받아 그대로 반환하고, b 리듀서는 state로 undefined를 받아 자신의 기본 state 값(이 예에서는 'wat')을 반환합니다. 이렇게 { a: 'horse', b: 'wat' }가 반환됩니다.
정리
요약하자면, Redux 컨벤션을 따르고 state 인자가 undefined일 때 초기 상태를 반환하도록 리듀서를 작성한다면(가장 쉬운 방법은 state 기본 인자 값을 지정하는 것), 결합된 리듀서에 유용한 동작을 얻게 됩니다. 이들은 createStore() 함수에 전달한 preloadedState 객체의 해당 값을 우선 사용하지만, 전달하지 않았거나 해당 필드가 설정되지 않은 경우 리듀서가 지정한 기본 state 인자가 대신 선택됩니다. 이 접근 방식은 초기화와 기존 데이터의 복원을 모두 제공하면서 개별 리듀서가 데이터가 보존되지 않았을 때 상태를 재설정할 수 있게 하므로 효과적입니다. 물론 combineReducers()를 여러 수준에서 사용하거나 수동으로 리듀서를 호출하면서 상태 트리의 관련 부분을 제공함으로써 이 패턴을 재귀적으로 적용할 수 있습니다.