[react] redux-toolkit with typescript

sue·2021년 4월 27일
0

react note

목록 보기
15/17

redux-toolkit 공식문서 내용을 개인 학습용으로 정리한 글입니다.

  • 배울 것
    Redux Toolkit 및 React-Redux를 TypeScript로 설정하고 사용하는 방법

프로젝트 설정

RootState 및 Dispatch 타입 정의

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>
>;

타입이 지정된 훅 정의

RootStateAppDispatch 타입을 각 컴포넌트로 가져올 수 있지만, 어플리케이션에서 사용할 useDispatchuseSelector 훅의 타입이 지정된 버전을 만드는 게 좋습니다. 이것은 몇 가지 이유로 중요합니다.

  1. useSelector => (state: RootState)를 매번 입력할 필요가 없습니다.
  2. 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

응용 프로그램 사용

slice state 및 action type 정의

각 슬라이스 파일은 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 렌더링 로직 생략
}

0개의 댓글