[React] Redux-toolkit in TypeScript

Dorong·2023년 2월 15일
0

React

목록 보기
21/29
post-thumbnail

😎 개요


✅ Redux Toolkit도 TypeScript로 만들어짐

  • Redux Toolkit is already written in TypeScript, so its TS type definitions are built in.

  • React Redux has its type definitions in a separate @types/react-redux typedefs package on NPM. In addition to typing the library functions, the types also export some helpers to make it easier to write typesafe interfaces between your Redux store and your React components.

    사전에 분리되 정의된 @types/react-redux npm 설치 필요

  • As of React Redux v7.2.3, the react-redux package has a dependency on @types/react-redux, so the type definitions will be automatically installed with the library. Otherwise, you'll need to manually install them yourself (typically npm install @types/react-redux ).

    근데 React Redux v7.2.3부터는 내장되어있음!!


✅ TS 적용된 Redux CRA으로 사용하기

  • The official Redux+TS template for Create React App.

  • To use this template within your project, add --template redux-typescript when creating a new app.


✅ Project Setup

🔸 Define Root State and Dispatch Types

  • Using configureStore should not need any additional typings.

  • You will, however, want to extract the RootState type and the Dispatch type so that they can be referenced as needed.

    다 괜찮은데 RootState와 Dispatch 타입은 지정해주자

  • Since those are types, it's safe to export them directly from your store setup file such as app/store.ts and import them directly into other files.

    store 셋업 파일에서 직빵으로 다른 파일에 export 해주는 게 안전

  • 코드 예시

    import { configureStore } from '@reduxjs/toolkit'
    // ...
    
    export const store = configureStore({
      reducer: {
        ...
      },
    })
    
    // Infer the `RootState` and `AppDispatch` types from the store itself
    export type RootState = ReturnType<typeof store.getState>
    // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
    export type AppDispatch = typeof store.dispatch

🔸 Define Typed Hooks

  • While it's possible to import the RootState and AppDispatch types into each component, it's better to create typed versions of the useDispatch and useSelector hooks for usage in your application.

    RootState와 AppDispatch타입들을 불러올 수 있으니, 타입이 지정된 useDispatch와 useSelector 훅들을 생성해두는게 좋다!!

  • Since these are actual variables, not types, it's important to define them in a separate file such as app/hooks.ts, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.

    그리고 이건 타입이 아니라 진짜 변수들이라서, store 셋업 파일이 아닌 별도의 파일로 분리해서 선언하는게 중요하다!!
    (다양한 컴포넌트에서 사용과 circular import dependency 이슈를 방지)

  • 코드예시

    // app/hooks.ts
    // Use throughout your app instead of plain `useDispatch` and `useSelector`
    export const useAppDispatch: () => AppDispatch = useDispatch
    export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector


✅ Application Usage

🔸 Define Slice State and Action Types

  • Each slice file should define a type for its initial state value, so that createSlice can correctly infer the type of state in each case reducer.

    각각의 slice는 자신의 inital state에 대한 타입 지정이 필요

  • All generated actions should be defined using the PayloadAction type from Redux Toolkit, which takes the type of the action. payload field as its generic argument.

    만들어진 모든 acrion들은 PayloadAction을 import 한 뒤 이를 사용해 타입을 지정해줘야함!!

  • You can safely import the RootState type from the store file here. It's a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions.

  • 코드예시

    import { createSlice } from '@reduxjs/toolkit'
    import type { 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`
        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
  • In some cases, TypeScript may unnecessarily tighten the type of the initial state. If that happens, you can work around it by casting the initial state using as, instead of declaring the type of the variable:

    // Workaround: cast state instead of declaring variable type
    const initialState = {
      value: 0,
    } as CounterState

🔸 Use Typed Hooks in Components

  • In component files, import the pre-typed hooks instead of the standard hooks from React-Redux.

    컴포넌트 파일에서는 Dispatch와 Selector 등을 이미 타입 지정된 것들로 import해서 사용해라~~

  • 코드 예시

    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
    }
profile
🥳믓진 개발자가 되겠어요🥳

0개의 댓글