웹/React

Redux 적용하기 with Typesciprt, Redux toolkit

개발하고 기록하는 개발자 2022. 4. 21. 01:15

1. Redux Toolkit 과 React-Redux 설치

npm install @reduxjs/toolkit react-redux

2. Redux Store 생성하기

// app/store.js 
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  reducer: {},
})

// store 에 있는 State 타입과 Dispatch 타입 지정 
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

3. React index 파일에 Provider 추가

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

4. Redux State Slice 파일 만들기

//features/counter/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`
    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
  • createSlice : slice 의 이름을 식별하기 위한 이름, 초기 값(initial state value), 하나 또는 여러개의 reducer 함수가 parameter 로 필요하다.
  • Redux Toolkit 의 createSlice 와 createReducer APIs 는 Immer 를 바로 사용할 수 있다.

5. Slice Reducers 를 store 에 추가하기

//app/store.js

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

6. React Components 에서 Redux State 와 Actions 사용하기

import React from 'react'
import { RootState } from '../../app/store'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useSelector((state: RootState) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

7. Typed Hooks 정의하기

//app/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

단순히 RootState 와 AppDispatch 를 각 Components 에서 import 해서 사용할 수도 있다. 하지만 Redux Toolkit 개발자들은 typed 된 useDispatch 와 useSelector hook 을 만들어 사용하는 것을 추천하고 있다 이유는 다음과 같다.

 

1. useAppSelector 를 정의하면 (state:RootState) 라고 매번 부를 필요가 없다

2. Default Dispatch 는 thunks 를 모른다. 그래서 thunks 를 올바르게 dispatch 하기 위해서는 thunk 미들웨어 타입이 포함된 store 에서 커스텀된 AppDispatch를 사용해야한다.

이때 useAppDispatch 와 같이 미리 typed 해 놓으면 AppDispatch 를 import 하는 것을 보장해준다.

※Thunk : 컴퓨터 프로그래밍에서, 썽크(Thunk)는 기존의 서브루틴에 추가적인 연산을 삽입할 때 사용되는 서브루틴이다.

 

※주의사항 : useAppDispatch 나 useAppSelector 처럼 pre-typed 하는 경우 꼭 store 설정 파일과 다른 파일에다가 정의해야한다
만약 같은 파일에 둔다면 import dependency issues 가 발생할 수도 있다.

 

8. Typed Hook 사용한 버전

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
}

참고 자료 : https://redux-toolkit.js.org/tutorials/typescript, https://redux-toolkit.js.org/tutorials/quick-start