[Redux Toolkit] 전역상태 관리하기

chosh·2023년 9월 3일

전역 상태를 효율적으로 관리하기 위해 고민했던 내용들을 정리해보았습니다.


간단하게 Redux toolkit은,

  1. Store를 설정

    import { configureStore } from '@reduxjs/toolkit';
    
    const store = configureStore({
      reducer: {
        counter: counterReducer,
      }
    });
    
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
  2. Provider 생성

    import React, {useEffect} from 'react';
    import Router from '@Router';
    import {store} from '@app/store';
    import {Provider} from 'react-redux';
    
    const App = () => {
      return (
        <Provider store={store}>
          <Router />
        </Provider>  
      )
    }
    
    export default App;
  3. Slice 생성

    import { createSlice } from '@reduxjs/toolkit'
    import type { PayloadAction } from '@reduxjs/toolkit'
    
    export interface CounterState {
      value: number
    }
    
    const initialState: CounterState = {
      value: 0,
    }
    
    export const counterSlice = createSlice({
      name: 'counter',
      initialState,
      reducers: {
        increment: (state) => {
          state.value += 1
        },
        decrement: (state) => {
          state.value -= 1
        },
        incrementByAmount: (state, action: PayloadAction<number>) => {
          state.value += action.payload
        },
      },
    })
    
    export const { increment, decrement, incrementByAmount } = counterSlice.actions
    export default counterSlice.reducer
  4. 리듀서 및 액션을 연결

    import React from 'react'
    import type { 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>
      )
    }

와 같이 사용하게 된다.


답답했던 과거의 내 코드

나는 전역상태 관리를 Redux Toolkit으로 처음 시작했는데 그 때 이런식으로 작성했다.(사라져버린 코드라 임의로 작성;;)

slice

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

export interface DataState {
  data1: string;
  data2: string;
  data3: string;
}

const initialState: DataState = {
  data1: "",
  data2: "",
  data3: "",
}

export const dataSlice = createSlice({
  name: 'data',
  initialState,
  reducers: {
    setData: (state, action: PayloadAction<DataState>) => {
      state = action.payload
    },
  },
})

export const { setData } = dataSlice.actions
export default dataSlice.reducer

사용

const App = () => {
  const data = useSelector((state: RootState) => state.data)
  const dispatch = useDispatch()

  return (
    <div>
      <button
        aria-label="데이터 전체 바꾸기"
        onClick={() => dispatch(setData({
          data1:"데이터1",
          data2:"데이터2",
          data3:"데이터3",
        }))}
        >
        데이터 전체 바꾸기
      </button>

      <button
        aria-label="데이터 일부 바꾸기"
        onClick={() => dispatch(setData({
		  ...data, data2:"data2",
        }))}
        >
        데이터 일부 바꾸기
      </button>
      
      <button
        aria-label="데이터 리셋"
        onClick={() => dispatch(setData({
          data1:"",
          data2:"",
          data3:"",
        }))}
        >
        데이터 리셋
      </button>
    </div>
  )
}

export default App;

이렇게 작성을 해서 Redux Toolkit을 사용했고 그 때는 하나의 함수로 여러가지 일을 모두 해낼수 있어서 그게 좋다고 생각하고 저렇게 사용했던것 같다;;

하지만 함수는 하나의 일을 해야 한다는 규칙이 있다.
그 외에도 여러가지 이유로 Reducer를 정의 하는 방법을 바꿔 사용했다.
그 이유는

  1. 늘어져 버린 코드로 좋지 않은 가독성
  2. 익숙해져버린 내가 볼땐 어떤 일을 하는지 바로 파악이 될지 모르지만, 다른 사람이 내 코드를 보거나 미래의 내가 봤을때 하는 일이 한눈에 파악될지는 미지수..
  3. 일부 데이터를 업데이트 할때 필요하지 않은 다른 데이터까지 얕은복사로 인해 새로 메모리에 할당해서 업데이트
  4. 초기값을 알고있어야 되는 리셋;;

이런 문제를 해결하기 위해 아래와 같이 수정하여 적용하였다.

수정한 코드

slice

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

export interface DataState {
  data1: string;
  data2: string;
  data3: string;
}

interface PatchData {
  key: keyof DataState;
  value: string;
}

const initialState: DataState = {
  data1: "",
  data2: "",
  data3: "",
}

export const dataSlice = createSlice({
  name: 'data',
  initialState,
  reducers: {
    putData: (state, action: PayloadAction<DataState>) => {
      state = action.payload
    },
    patchData: (state, action: PayloadAction<PatchData>) => {
      state[action.payload.key] = action.payload.value
    },
    resetData: (state) => {
      state = initialState;
    },
  },
})

export const { putData, patchData, resetData } = dataSlice.actions
export default dataSlice.reducer

사용

const App = () => {
  const dispatch = useDispatch()

  return (
    <div>
      <button
        aria-label="데이터 전체 바꾸기"
        onClick={() => dispatch(putData({
          data1:"데이터1",
          data2:"데이터2",
          data3:"데이터3",
        }))}
        >
        데이터 전체 바꾸기
      </button>

      <button
        aria-label="데이터 일부 바꾸기"
        onClick={() => dispatch(patchData({
		  key: "data2",
          value: "data2",
        }))}
        >
        데이터 일부 바꾸기
      </button>
      
      <button
        aria-label="데이터 리셋"
        onClick={() => dispatch(resetData())}
        >
        데이터 리셋
      </button>
    </div>
  )
}

export default App;

이렇게 하면 함수마다 하나의 일을 수행하고 있고,
함수명을 보고 어떤일을 하는지 알수 있고,
불필요한 데이터를 새로 생성하는일도 없어지고,
초기값을 알아야 되는 일도 없어지게 된다.

그리고 각 함수마다 하는 일을 알고 싶으면 정의된 slice 파일에 가서 한번에 전체적인 흐름을 파악할 수 있기 때문에 훨씬 가독성이 개선됐다고 느꼈다.


이 코드가 베스트라고 생각하진 않지만, 이렇게 이전에 개발했던 것들을 불편하다고 느낄 때 개선하고자 한다면 차츰차츰 발전해 나가는 코드를 작성할 수 있을거 같다.

profile
제가 참고하기 위해 만든 블로그라 글을 편하게 작성했습니다. 틀린거 있다면 댓글 부탁드립니다.

0개의 댓글