리듀서 합치기
combineReducers
combineReducers() 함수는 여러 리듀서를 통합하여 새로운 리듀서를 만들어 준다. 이 함수는 ReducerMapObject 타입을 매개변수로 받는다. 여기서 타입변수 S는 상태(AppState)를 의미한다.
// combineReducers 함수 선언문
export function combineReducer<S>(reducers: ReducersMapObject<S, any>):
Reducer<CombinedState<S>>
// ReducersMapObject 타입 선언문
export type ReducersMapObject<State = any, A extends Action = Action> = {
[Key in keyof State]: Reducer<State[Key], A>
}
AppState.ts
4개의 멤버 상태로 구성했으므로 각각 처리하는 4개의 리듀서가 필요하다.
import * as Clock from './clock'
import * as Counter from './counter'
import * as R from './remoteUser'
import * as Cards from './cards'
export type AppState = {
clock: Clock.State
counter: Counter.State
remoteUser: R.State
cards: Cards.State
}
rootReducer.ts
[Key in keyof State]: Reducer<State[key], A> 부분을 고려해 보면, clock, counter 등의 멤버 상태는 모두 AppState의 키이므로, [Key in keyof State] 조건을 만족한다. 또한 각 키 설정값의 타입은 Reducer<State[Key],A> 즉, 리듀서 함수여야 한다.
import {combineReducers} from "redux";
import * as Clock from './clock'
import * as Counter from './counter'
import * as R from './remoteUser'
import * as Cards from './cards'
export const rootReducer = combineReducers({
clock: Clock.reducer,
counter: Counter.reducer,
remoteUser: R.reducer,
cards: Cards.reducer
})
clock/types.ts
시계를 그리기 위해 사용될 상태, 액션의 타입을 정의했다.
import type {Action} from "redux";
export type State = string
export type SetClockAction = Action<'@clock/setClock'> & {
payload: State
}
export type Actions = SetClockAction
clock/actions.ts
action을 생성하는 함수이다.
import type * as T from './types'
export const setClock = (payload: T.State): T.SetClockAction => ({
type: '@clock/setClock',
payload
})
clock/reducer.ts
@clock/setClock을 처리하는 리듀서 함수이다.
import type * as T from './types'
const initialState: T.State = new Date().toISOString()
export const reducer = (state: T.State = initialState, action: T.Actions) => {
switch (action.type){
case "@clock/setClock":
return action.payload
}
return state
}
clockTest.tsx
useSelector를 통해 초기에 ISOString을 가져와서 랜더링한다.
이후 1초 마다 dispatch 함수를 통해 새로운 ISOString을 업데이트하고 있다.
import {useDispatch, useSelector} from "react-redux";
import type {AppState} from "../store";
import * as C from '../store/clock'
import {useInterval} from "../hooks";
import {Title} from "../components";
export default function ClockTest() {
const clock = new Date(useSelector<AppState, C.State>(state => state.clock))
const dispatch = useDispatch()
useInterval(() => dispatch(C.setClock(new Date().toISOString())))
return (
<section className={'mt-4'}>
<Title>ClockTest</Title>
<div className={"flex flex-col items-center mt-4"}>
<p className={"text-2xml text-blue-600 text-bold"}>
{clock.toLocaleTimeString()}
</p>
<p className={"text-lg text-blue-400 text-bold"}>
{clock.toLocaleDateString()}
</p>
</div>
</section>
)
}
@이름/' 접두사와 payload라는 변수 이름을 사용하는 이유
리듀서에는 액션이 유입되면, 특정 리듀서뿐만 아니라 combineReducers()가 결합한 모든 리듀서에 액션이 전송된다. 즉, 앱에 구현된 아무 리듀서에나 console.log 코드로 출력해 보면 앱에서 dispatch 함수로 전송되는 모든 액션을 콘솔창에서 확인해 볼 수있다.
따라서 액션 타입을 평범하게 지으면 이름 충돌이 발생할 수 있다. 때문에 @이름/ 접두사를 type 이름 앞에 붙이는 것이다.
payload라는 변수명을 사용하는 이유
AppState를 구성하는 멤버 상태의 타입은 개발 초기에는 Date, number 등의 단순한 타입이지만, 개발 후기에는 다른 속성들이 추가되어 좀 더 복잡한 상태로 변경될 수 있다. 이러한 상태의 이름은 clock.clock, operand.operand처럼 의미가 이상한 이름이 된다. 반면에 payload는 payload.clock, payload.operand처럼 훨씬 더 자연스러운 이름이 된다.
리듀서는 순수 함수여야 한다
리덕스는 리덕스 저장소에 저장된 과거 상태와 리듀서 함수가 반환하는 현재 상태를 if(A!==B) 방식으로 비교한다. 이러한 비교가 가능하려면 리듀서 함수 내부에서 현재 상태는 과거 상태를 깊은 복사 해야 하며, 때문에 리덕스의 리듀서는 반드시 순수함수여야 한다.
728x90
'Javascript > React' 카테고리의 다른 글
[React] React DnD 라이브러리 (0) | 2024.07.31 |
---|---|
[React] 리덕스 미들웨어 (0) | 2024.07.29 |
리덕스 기본 개념 이해하기 (2) | 2024.07.21 |
useContext (0) | 2024.07.21 |
useRef, useImperativeHandle, forwardRef (0) | 2024.07.20 |