[Redux] Redux-Toolkit 이란?

SeHoony·2021년 10월 6일
5

개념 각인

목록 보기
2/3
post-thumbnail

React 프로젝트에 있어 '상태관리'는 중요한 개념이다. 앞서 설명했던 Context-API도 상태관리를 위한 유용한 방법이지만, 이번에는 'Redux-Toolkit'에 대해 알아보고자 한다. 이번 포스팅에서는 slice의 기본 사용방법을 확인하기 위해 counter 프로젝트를 만들어 보는 것을 목표로 한다.

1. 개괄

이번 Redux-toolkit(=RTK) 포스팅은 'slice'에 초점을 맞춘다. RTK에는 createAction, createReducer 등도 존재하지만 이것들을 합친 것이 createSlice이고 훨씬 더 간편하다. 다음은 creatSlice를 이용하여 counter 프로젝트를 만들기 위한 과정의 개괄이다.

1. set up(Redux Toolkit library)

2. counterSlice 생성
👉 2.1. initial state type 지정
👉 2.2. createSlice -> action.payload type 지정
👉 2.3. export : actions, reducer of slice

3. store 생성
👉 3.1. configureStore -> store 생성
👉 3.2. RootState, AppDispatch 생성 및 export

4. store 연결 : index.ts에서 Provider를 통해 store 연결

5. [사용 단계] hooks 생성 : useSelector, useDispatch를 개별 컴포넌트에서 편하게 사용하기 위해 따로 만들어 준다.

6. [사용 단계] view 구현 : View에서 위의 5에서 생성한 hook을 통해 store 내의 값을 불러온다.

2. Counter 프로젝트 구현

2-1. SET UP

프로젝트 생성 및 redux-toolkit

2-1-1. 프로젝트 생성

npx create-react-app slice_practice --template typescript

2-1-2. 라이브러리 설치

첫번째는 Typescript를 이용한 react-redux 관련 라이브러리 설치 명령어이다. 두번째는 redux-toolkit을 사용하기 위한 라이브러리 설치이며, 이것이 설치되면 createSlice 메서드를 이용가능하다. 마지막으로 세번째와 네번째 라이브러리를 차례로 설치한다.

yarn add @types/react-redux
yarn add @reduxjs/toolkit
yarn add redux-typescript
yarn add react-redux

2-2. createSlice : counterSlice 생성

src 디렉토리 하단에 features - counter - counter.ts 파일을 생성한다.

전체 스크립트부터 확인하고, 위의 개괄에서 살펴본 바와 같이 코딩 순서 3단계로 나누어 살펴보겠다.

// counter.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit"

// 1. initial state type 생성
export type counterState = {
  value: number
}
// 1-1. initial state 객체 생성
const initialState: counterState = {
  value: 0,
}

// 2. slice 생성 : createSlice
const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByValue: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// 3. export

// 3-1. export actions
export const { increment, decrement, incrementByValue } = counterSlice.actions
// 3-2. export default slice.reducer
export default counterSlice.reducer

2-2-1. initial state type 지정

initial state의 type을 미리 정해놓고, initial state 객체까지 생성한다.

// 1. initial state type 생성
export type counterState = {
  value: number
}
// 1-1. initial state 객체 생성
const initialState: counterState = {
  value: 0,
}

2-2-2. createSlice : slice 생성

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByValue: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

createSlice는 name, initialState, reducers 세 요소를 요구한다.
name은 action 앞에 첨가되어 다른 slice의 action들과 이름 중복을 피하는 역할을 한다. initialState에는 미리 생성한 initialState를 넣는다. 그리고 reducers의 각각은 action 역할을 하며 동시에 state의 변화까지 구현한다. 그리고 immer.js를 내장하고 있기 때문에 state 값을 return하지 않아도 된다.

2-2-3. export

slice 내의 actions들과 reducer를 export한다.

// 3-1. export actions
export const { increment, decrement, incrementByValue } = counterSlice.actions
// 3-2. export default slice.reducer
export default counterSlice.reducer

2-3. store 생성

store은 redux에서 state와 dispatch할 함수들을 가지는 저장소 역할을 한다. typeScript 환경 내에서는 store 내에서 state, dispatch의 type을 지정해줘야 한다.

src - app - store.ts 생성한다.

2-3-1. ConfigureStore : store 생성 메서드

Redux-toolkit에서는 createStore 대신으로 configureStore을 사용하여 store을 손쉽게 생성가능하다.

import { configureStore } from "@reduxjs/toolkit"
import CounterReducer from "../features/counter/counter"

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

2-3-2. RootState, AppDispatch type 설정

앞서 설명한 바와 같이 store는 state와 dispatch를 보관하는데 가장 큰 의미가 있다. 따라서 typeScript 환경 내에서는 이 둘의 type을 밝혀준다.

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

2-4. Provider 생성 : store와 app 연결

지금까지 만들어놓은 store와 컴포넌트들을 연결함으로써, 컴포넌트들이 store 내의 state나 dispatch 함수들을 자유롭게 사용할 수 있도록 한다. 이 때, 가장 상위 컴포넌트에 store을 연결하면 하단의 컴포넌트 모두 자동 연결되기 때문에 index.tsx에서 Provider를 생성하여 store을 연결한다.

// index.tsx
import {Provider} from "react-redux"
import {store} from './app/store'

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

이것으로 counterSlice를 어떤 컴포넌트에서도 쓸 수 있도록 준비가 끝났다.

2-5. Hooks : useSelector, useDispatch

store내의 state와 dispatch를 쓰기 위해서는 useSelector와 useDispatch를 사용한다. 이를 사용하는 매 컴포넌트마다 계속 적기 힘들기 때문에 따로 custom hook을 만들어 관리한다.

src - app -hooks.ts 생성한다.

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

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

2-6. Usage in components

각 컴포넌트에서 useAppSelector와 useAppDispatch를 이용하여 store의 값들을 사용한다. 해당 코드는 간단하니 어떻게 사용하면 되는 지 한 번 훑어보는 것으로도 충분할 것이다.

import React, { useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../app/hooks'

import {increment, decrement, incrementByValue} from './counter'

function CounterView() {
    const count = useAppSelector(state => state.counter.value)
    const dispatch = useAppDispatch()

    const [num, setNum] = useState<number>(0);
    const handleChange = (e:React.ChangeEvent<HTMLInputElement>) =>{
        setNum(parseInt(e.currentTarget.value))
    }

    return (
        <>
        <div style ={{display : 'flex', justifyContent :"center", alignContent : "center"}}>
            <button onClick ={()=>dispatch(decrement())}>-1</button>
            <h1 style = {{margin : "3em"}}>{count}</h1>
            <button onClick ={()=>dispatch(increment())}>+1</button>  
        </div>
        
        <div>
            <input type="number" onChange = {handleChange}/>
            <button onClick={()=>dispatch(incrementByValue(num))}>+{num}</button>
        </div> 
    </>
    )
}
export default CounterView

3. Conclusion!

이번 주는 집중력이 부족하다. createSlice를 몇 번이나 하고, 같은 예제를 몇 번 반복했음에도 이번 포스팅을 하는 도중에도 몇 분동안 막혀서 답답했었다. 그러니까 첫 술에 배부르려 하지말고 찬찬히 보고 지속적으로 이용해서 꼭 내 것으로 만들고 싶다. 상태관리 라이브러리를 여러개 이용해봤는데, 이번 redux-toolkit이 가장 쉽게 이해되면서 기존의 redux와 궤를 같이 하기 때문에 접근성도 좋다고 생각한다. 당분간은 redux-slice를 쓸 생각이다!

profile
두 발로 매일 정진하는 두발자, 강세훈입니다. 저는 '두 발'이라는 이 단어를 참 좋아합니다. 이 말이 주는 건강, 정직 그리고 성실의 느낌이 제가 주는 분위기가 되었으면 좋겠습니다.

1개의 댓글

comment-user-thumbnail
2023년 1월 28일

로그인 로직을 짤때 redux-toolkit 를 썼는데 쓰면서도 이해가 잘 되질않아 검색하던중 이 글을 발견했는데 발견해서 너무 다행이네요 !! 덕분에 조금더 개념 정리가 되었습니다! 감사합니다 :)

답글 달기