이 글은 리액트를 기준으로 작성되었습니다.
앞선 두 글에서 Redux Toolkit 에 대해 알아봤다.
위 글에서는 예제를 자바스크립트로 작성했는데, 오늘은 타입스크립트를 사용하는 방법에 대해 알아보고자 한다.
패키지 설치
프로젝트에 v7.2.3 이상의 react-redux
와 @reduxjs/toolkit
을 설치했다면 (혹은 dependency에 추가되어있다면)
react-redux
이 @types/react-redux
를 dependency로 가지고 있기 때문에 리액트 툴킷에서 타입스크립트를 사용하기 위해 별도로 설치해야하는 패키지는 없다.
만약 react-redux
버전이 v7.2.3 보다 낮다면 그 이상 버전을 사용하거나 @types/react-redux
를 설치해주자.
store.ts
store.ts는 기존의 자바스크립트 코드와 같이 작성하고, state와 dispatch 타입을 store에서 추론(Infer)해서 export한다. (마지막 두 줄)
import { configureStore } from '@reduxjs/toolkit'
// ...
export const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer,
users: usersReducer
}
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
hooks.ts
컴포넌트에서 데이터를 불러오기 위해는 useSelector
를, action을 dispatch하기 위해서는 useDispatch
를 사용한다.
그런데 RTK(Redux toolkit)를 타입스크립트와 사용하려면 useSelector
와 useDispatch
를 타입스크립트 버전으로 바꿔줘야한다.
이런 타입화 과정을 모든 컴포넌트에 적용하는 것보다는 hooks.ts라는 파일을 만들어 pre-typed 된 버전을 만들어주는 것이 편하다.
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
그런 다음 컴포넌트에서 아래와 같이 pre-타입화된 dispatch와 selector를 사용한다.
// components/counter.tsx
// ...
import { useAppSelector, useAppDispatch } from '../hooks'
export function Counter() {
const count = useAppSelector(state => state.counter.value)
const dispatch = useAppDispatch()
// ...
}
Slice 생성
slice의 state에 타입을 정의한다.
// features/counter/counterSlice.ts
// ...
interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0
}
그리고 reducer에서 payload를 인자로 받는 경우 파라미터에 아래와 같이 action.payload의 타입을 정의해준다.
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// ...
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
// ...
Thunk
thunk 함수를 사용한다면 createAsyncThunk
에서 타입 정의가 필요하다.
1. thunkAPI를 사용하지 않고 인자값만 받는 경우
// ...
const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId: number) => { // 인자의 타입을 정의
const response = await fetch(`https://reqres.in/api/users/${userId}`)
return (await response.json()) as Returned // 리턴 타입 정의
}
)
// ...
인자값의 타입과 리턴 타입을 정의해준다.
2. thunkAPI를 사용하는 경우
// ...
import type { AppDispatch, RootState } from "../../store";
// ...
const fetchUserById = createAsyncThunk<
MyData, // 리턴 타입
number, // 첫번째 인자의 타입
{
// thunkApi 필드 타입 정의
dispatch: AppDispatch
state: RootState
extra: {
jwt: string
}
}
>('users/fetchById', async (userId, thunkApi) => {
const response = await fetch(`https://reqres.in/api/users/${userId}`, {
headers: {
Authorization: `Bearer ${thunkApi.extra.jwt}`,
},
})
return (await response.json()) as MyData
})
thunkAPI 타입을 정의하기 위해서는 아래 AsyncThunkConfig
중 사용할 필드를 정의한 객체를 제네릭 인자 세번째에 추가해준다.
type AsyncThunkConfig = {
state?: unknown
dispatch?: Dispatch
extra?: unknown
rejectValue?: unknown
serializedErrorType?: unknown
pendingMeta?: unknown
fulfilledMeta?: unknown
rejectedMeta?: unknown
}
- state
: `thunkApi.getState`의 리턴 타입
- dispatch
: `thunkApi.dispatch`의 타입
- extra
: thunk middleware를 위한 `thunkApi.extra` 인자의 타입
- rejectValue
: `rejectWithValue`의 첫번째 인자, 즉 `rejectedAction.payload`의 타입
- serializedErrorType
: `serializeError` option 콜백의 리턴 타입
Reference
- [Redux Docs] Usage With TypeScript
- [Redux Toolkit Docs] Usage With TypeScript - type safety with extraReducers
해당 글의 모든 소스 코드 출처는 Redux, Redux Toolkit 공식 문서에 있습니다.
'개발 > 프론트엔드 (JS & React)' 카테고리의 다른 글
[React] CRA로 생성한 프로젝트 Vite로 마이그레이션하기 (타입스크립트) (1) | 2022.11.16 |
---|---|
[React] 리액트 프로젝트 생성 - Create React App 보다 더 빠른 Vite 사용하기 (0) | 2022.11.15 |
[Redux toolkit] 2 - Slice, Reducer, Action, Thunk란? (0) | 2022.11.11 |
[Redux Toolkit] 1 - Redux Toolkit이란? | Redux와 Redux Toolkit 차이 (1) | 2022.11.11 |
[React + MongoDB + Netlify] Netlify Serverless 함수와 몽고DB를 사용해 리액트 프로젝트 무료로 호스팅하기 2 (0) | 2022.11.09 |