redux-toolkit 공식문서 내용을 개인 학습용으로 정리한 글입니다.
configureStore
를 사용하면 추가 입력이 필요하지 않습니다. 그러나 필요에 따라 참조할 수 있도록 RootState
타입과 Dispatch
타입을 추출할 수 있습니다. 스토어 자체에서 이러한 타입을 추론하면 state slice를 더 추가하거나 미들웨어 설정을 수정할 때 올바르게 업데이트됩니다.
이들은 app/store.ts
와 같은 스토어 설정 파일에서 직접 내보내고, 다른 파일로 직접 가져 오는 것이 안전합니다.
app/store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export type AppDispatch = typeof store.dispatch; // Type to access dispatch
export type RootState = ReturnType<typeof store.getState>; // A global type to access reducers types
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
RootState
나 AppDispatch
타입을 각 컴포넌트로 가져올 수 있지만, 어플리케이션에서 사용할 useDispatch
나 useSelector
훅의 타입이 지정된 버전을 만드는 게 좋습니다. 이것은 몇 가지 이유로 중요합니다.
useSelector
=> (state: RootState)
를 매번 입력할 필요가 없습니다.useDispatch
=> 기본 Dispatch
타입은 thunk를 알지 못합니다. thunk를 올바르게 디스패치 하려면, 스토어에서 thunk 미들웨어 타입을 포함하는 특정 사용자 정의 AppDispatch
타입을 사용해야 하고, 이것을 useDispatch
와 함께 써야합니다.useDispatch
hook을 추가한다면, AppDispatch
를 필요한 곳에 가져오는 것을 잊을 일이 없습니다.이는 타입이 아니라 실제 변수이므로, app/hooks.ts
와 같이 store가 아닌 별도의 파일에서 정의하는 것이 중요합니다.
이를 통해 훅을 사용해야 하는 모든 컴포넌트 파일로 가져올 수 있으며, 잠재적인 순환 import 종속성 문제를 방지할 수 있습니다.
app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
각 슬라이스 파일은 createSlice
가 각각의 리듀서에서 state
의 타입을 올바르게 추론 할 수 있도록 초기 상태 값에 대한 타입을 정의해야 합니다.
생성된 모든 액션은 action.payload
필드를 제네릭 인수로 사용하는 Redux Toolkit의 PayloadAction<T>
타입을 사용하여 정의해야 합니다.
여기에 있는 스토어 파일에서 RootState
타입을 안전하게 가져올 수 있습니다. 순환 임포트이지만, TypeScript 컴파일러는 타입에 대해 올바르게 처리 할 수 있습니다. selector 함수 작성과 같은 사용 사례에 필요할 수 있습니다.
counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { RootState } from '../../app/store'
// Define a type for the slice state
interface CounterState {
value: number
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
}
export const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
// PayloadAction 타입을 사용하여 `action.payload`의 내용을 선언
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value
export default counterSlice.reducer
생성된 액션 생성자는 PayloadAction<T>
타입에 따라 payload
인수를 허용하도록 올바르게 입력되어 reducer에 제공됩니다.
예를 들어, incrementByAmount
의 인수로 number
가 필요합니다.
경우에 따라 TypeScript는 초기 상태의 타입을 불필요하게 강화할 수 있습니다. 이 경우 변수 타입을 선언하는 대신 as
를 사용하여 초기 상태를 캐스팅하여 문제를 해결할 수 있습니다.
컴포넌트 파일에서 React-Redux의 표준 훅 대신 미리 타입화된 훅을 가져옵니다.
features/counter/Counter.tsx
import React, { useState } from 'react'
import { useAppSelector, useAppDispatch } from 'app/hooks'
import { decrement, increment } from './counterSlice'
export function Counter() {
// The `state` arg is correctly typed as `RootState` already
const count = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
// omit rendering logic 렌더링 로직 생략
}