Kimsora✨
article thumbnail
Published 2022. 12. 19. 00:16
redux-toolkit React
320x100
반응형

Redux Toolkit은 Redux를 더 사용하기 쉽게 만들기 위해 Redux에서 공식 제공하는 개발도구이다

 

Redux 사용시 문제점

  • 저장소 구성의 복잡성
  • 많은 패키지 필요성(의존성)
  • 한 작업 시 필요한 수 많은 코드양(boilerplate)=>보일러플레이트

    보일러플레이트란?

    컴퓨터 프로그래밍에서 보일러플레이트 또는 보일러플레이트 코드라고 부르는 것은 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말한다.
//리덕스 설치
npm install @reduxjs/toolkit

 

리덕스 툴킷 api

  1. configureStore() : 리덕스 createStore함수와 비슷한 함수로, 간단화된 구성 옵션과, 기본 구성을 제공한다. slice reducer를 자동으로 합치고, 미들웨어를 추가할 수 있으며, redux-thunk를 기본적으로 제공한다. 또한 redux devtools Extension 사용이 가능하다.=>예전엔 import 해옴

  2. createReducer() : 리듀서 함수를 switch 구문으로 쓰기보다는, 리듀서 함수를 계속쓰는 lookup table 방식(주어진 연산에 대해 미리 계산된 결과들의 집합,배열)을 쓸 수 있게 해주고, immer라이브러리가 내장되어 있어서 mutative한 코드를 작성할 수 있도록 해준다.

  3. createAction() : 주어진 액션 타입 문자열로 액션 크리에이터 함수를 생성해준다. 함수 자체에 toString()이 정의되어 있어서 constant 타입 대신 사용이 가능하다.

  4. createSlice() : reducer 함수, slice 이름, 초깃값을 넣을 수 있고 action creator와 action type을 가진 slice reducer를 자동으로 생성해준다.

  5. createAsyncThunk : redux-thunk의 대체재

  6. createEntityAdapter : 스토어에서 정규화된 데이터를 관리하기 위해 재사용 가능한 리듀서 및 selector 집합을 생성한다.

  7. createSelector : reselect 라이브러리의 유틸리티 기능과 똑같다


리덕스 미들웨어 (Redux Middleware)

dispatch(이하 디스패치)된 액션이 리듀서에 도달하기 전 중간 영역에서 사용자의 목적에 맞게 기능을 확장할 수 있도록 도와준다

ex)미들웨어로 redux-logger를 추가했다면 액션이 디스패치될 때마다 개발자 도구 콘솔에 로그가 찍히는 것을 생각해 볼 수 있다. 로그를 출력하는 과정이 중간에 추가된 것 이처럼 개발자는 자신의 필요에 의해 미들웨어를 작성하여 원하는 목적을 달성할 수 있다

createAction

기존

const INCRESE = 'counter/increse' 

function increse(amount) { 
  return {
    type: INCREMENT,
    payload: amount
  }
}

const action = increse(3) 
// { type: 'counter/increse', payload: 3 }

=>action함수를 정의하기 위해  별도의 액션타입을 설정해줘야 한다

변경

const increse = createAction('counter/increse') 

let action = increse() 
// { type: 'counter/increse' }

action = increse(3)
// returns { type: 'counter/increse', payload: 3 }

=>type만 인자로 넣어주면 default로 type을 가진 action object(액션함수)를 생성해주며
만약 이 액셤함수를 호출 시 parameter를 추가로 넣어준다면 자동으로 payload의 value값으로 들어간다.

createReducer

기존

function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'increse':
      return state + action.payload
    case 'decrese':
      return state - action.payload
    default:
      return state
  }
}

=>reducer 사용시 switch 등의 조건문으로 action type을 구분해 특정 로직 작성, default를 항상 명시 해주어야 한다.

변경

const counterReducer = createReducer(0, {
  increse: (state, action) => state + action.payload,
  decrese: (state, action) => state - action.payload
})

=>별도 조건문 필요 없이 첫 번째 인자는 initailState, 두 번째 인자는 caseReducers를 가진다.

createAction + createReducer

const increment = createAction('increse')
const decrement = createAction('decrese')

const counterReducer = createReducer(0, {
  [increse]: (state, action) => state + action.payload,
  [decrese.type]: (state, action) => state - action.payload
})

createSlice = action + reducer

createSlice는 Ducks 패턴을 사용해 action과 reducer를 전부 가진 함수이다.

기본형태

createSlice({
    name: 'reducerName',
    initialState: [],
    reducers: {
        action1(state, payload) {
            //action1 logic
        },
        action2(state, payload) {
            //action2 logic
        },
        action3(state, payload) {
            //action3 logic
        }
    }
})

=>name 속성은 액션의 경로를 잡아줄 이름이고 initialState은 초기 state를 나타낸다.reducer는 이전의 리듀서 함수와 같이 액션에 따라 특정 로직을 수행하는 것은 같으나기존에는 액션생성함수와 액션 타입을 선언해 사용했다면 createSlice는 액션을 선언하고 해당 액션이 dispatch 되면 바로 state을 가지고 해당 액션을 처리한다.

reducers 안의 코드들은 Action Type, Action Create Function, Reducer 의 기능이 합쳐있다

configureStore

기존

import { createStore } from "redux";
import rootReducer from "./Redux/Reducer";

const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(rootReducer, devTools);

 store를 생성할 때 redux가 제공하는 createStore을 이용해 생성하며 redux가 제공하는 combineReducers로 리듀서 함수를 묶어준 root리듀서를 인자로 넣는다. devTools 사용 시 두번째 인자로 넣어준다.

변경

import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todosReducer,
  },
});

별도의 combineReducers로 리듀서를 묶어줄 필요 없이 reducer 필드를 필수적으로 넣고 그안에 리듀서 함수를 넣으면 된다.
default로 redux devtool을 제공한다.

기존

//액션타입 
const increase = "INCREASE";
const decrease = "DECREASE";
const increaseFive = "INCREASE_FIVE";

//액션생성함수
export const increaseAction = () => {
  return { type: increase };
};

export const decreaseAction = () => {
  return { type: decrease };
};

export const increaseOtherAction = (number) => {
  return { type: increaseFive, data: number };
};

//초기 상태값
const initialState = {
  number:10,
};

//리듀서
export const numberReducer = (state = initialState, action) => {
  switch (action.type) {
    case increase:
      return {
        ...state,
        number: state.number + 1,
      };

    case decrease:
      return (state = {
        ...state,
        number: state.number - 1,
      });

    case increaseFive:
      return (state = {
        ...state,
        number: state.number + action.data,
      });

    default:
      return state; 
  }
};

name : 해당 모듈의 이름 작성
initialState : 해당 모듈의 초기값을 세팅
reducers : 리듀서를 작성하고 이때 해당 리듀서의 키값으로 액션함수가 자동으로 생성됨..
extraReducers : 액션함수가 자동으로 생성되지 않는 별도의 액션함수가 존재하는 리듀서를 정의 (선택 옵션)

 

변경

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppThunk, RootState } from '../../app/store';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  }; 
  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;

createAsyncThunk

기존

기존 redux에서 비동기 처리를 할 경우 thunk, saga, redux-observable 등의 미들웨어를 사용하여 한 개의 비동기 액션에 대해 pending, success, failure의 상태를 생성하여 처리하였다. 이때 각 상태를 만드는 것을 유틸 패키지를 받거나 직접 구현해야 했다

 

변경

리덕스 툴킷에서는 createAsyncThunk를 제공하여 Thunk의 미들웨어를 간편하게 사용할 수 있다

=> 다른 것은 이전 버전과 같이 직접 구현하거나 패키지를 받아야 한다
액션에 대한 상태의 이름을 지정하여 각기 다른 이름으로 생기는 혼란을 방지한다
=>pending: 비동기 호출전, fulfilled: 비동기 호출성공, rejected: 비동기 호출실패

 

export const fetchRecipes: any = createAsyncThunk<
  any
>(
  'recipes/fetchRecipes', // 액션 이름을 정의한다. 
  async () => { // 비동기 호출 함수를 정의 
    try {
      const response = await fetch("https://www.themealdb.com/api/json/v1/1/search.php?s=")
      const data = await response.json()
      console.log("data: ", data)
      return data.meals
    } catch (error) {
    }
  },
);

=>위와 같이 createAsyncThunk 를 선언하게 되면 첫번째 파라미터로 선언한 액션 이름 에 pending, fulfilled, rejected 의 상태에 대한 action 을 자동으로 생성해주게 된다.

createSlice + createAsyncThunk

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

const asyncUpFetch = createAsyncThunk('counterSlice/asyncUpFetch', async () => {
  const resp = await fetch(
    'https://api.countapi.xyz/hit/opesaljkdfslkjfsadf.com/visits'
  );
  const data = await resp.json();
  return data.value;
});

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
    status: 'Welcome',
  },
  reducers: {
    up: (state, action) => {
      state.value = state.value + action.payload;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(asyncUpFetch.pending, (state,action)=>{
      state.status = 'Loading';
    })
    builder.addCase(asyncUpFetch.fulfilled, (state,action)=>{
      state.value = action.payload;
      state.status = 'complete';
    })
    builder.addCase(asyncUpFetch.rejected, (state,action)=>{
      state.status = 'fail';
    })
  }
});
export default counterSlice;
export const { up, set } = counterSlice.actions;
export { asyncUp, asyncUpFetch };

https://jwdevv.tistory.com/36

728x90
반응형

'React' 카테고리의 다른 글

Intersection Observer API 과 무한스크롤 구현  (0) 2023.01.08
redux persist  (0) 2022.12.19
Fragment/Portal  (0) 2022.12.04
Custom Hooks와 코드 분할/React.lazy()와 Suspense  (0) 2022.11.29
useMemo/useCallback  (0) 2022.11.28
profile

Kimsora✨

@sorarar

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그

WH