티스토리 뷰

 

전반적으로 필요한 상태가 아니라 해당 컴포넌트에만
필요한 상태관리는 리덕스가 아니라 useState를 사용하는 것이 맞다.

기본 리덕스 용어 

  • state (상태) - 앱을 구동하는 소스, 특정 시점의 앱 상태를 나타냄
  • view - 현재 상태를 기반으로 화면에 보이는 UI, 해당 상태를 기반으로 렌더링됨-> UI는 새 상태를 기반으로 다시 렌더링된다.
  • action - 사용자 입력을 기반으로 앱에서 발생하는 이벤트 및 상태에서 업데이트를 발생시킨다.

단방향 데이터흐름의 작은 예시

-> 동일한 상태를 공유하고 사용해야 하는 여러 구성 요소가 있는 경우, 특히 해당 구성 요소가 app의 다른 부분에 있는 경우 단순성이 무너질 수 있다. 

 

JS의 객체와 배열은 기본적으로 모두 변경 가능하다.(mutable)

하지만 리덕스의 리듀서(reducer)는 기존 state를 수정할 수 없고(불변성유지) 대신 기존 값을 복사하여 복사된 값을 변경해서 새로운 state를 반환해야 합니다(이 외에는 그냥 기존 state를 리턴해주면 됩니다.) 

참고로 리듀서에서는 비동기 논리를 수행하거나 임의 값을 계산하거나(e.g.Math.random으로 난수발생시키기 등) 하는 등의 기타 부작용을 일으키면 안됩니다. 

// 리덕스 리듀서 예시
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}
redux의 reducer 함수는 Array.reduce()의 콜백줄이기 기능과 정확히 같은 개념이라고 합니다. -> "이전 결과(the state) 및 현재 항목(action 객체)"을 취하여 해당 인수를 기반으로 새 상태 값을 결정하고 새 상태를 반환합니다.

리덕스 툴킷은 왜 쓰나요?

리덕스 툴킷은 기존 리덕스의 단점들.. 불변성을 지키기위해 ... state를 사용해서 매번 새로운 state를 만들어줘야 하는 것과 아주 작은 기능이라도 리덕스로 구현하는 순간은 몇 개의 파일들을 필수로 만들어줘야 하는 점 그리고 대표적으로 보일러 플레이트 코드(액션 타입, 액션 생성함수, 리듀서)를 많이 준비해야하는 번거로움 등의 이유로 등장하게 됐다. 

 

장점: 리덕스 툴킷을 사용하면 리듀서, 액션타입, 액션 생성함수, 초기상태를 하나의 함수로 편하게 선언할 수 있다. 

위의 4가지를 통틀어서 slice라고 부른다. 

  • slice    =     ( reducer + action type + action creator + initial state) 
  • action type이나 action creator를 따로 생성해주지 않아도 된다.
  • 미들웨어 추가가 편리함
  • redux thunk가 내장되어 있어서 비동기 지원
  • 타입스크립트 지원 잘 됨
  • immer가 내장되어 있어서 mutable 객체를 사용해도 된다.
    • 알아서 내부적으로 불변성을 유지하기 때문에 원하는 값을 직접 변경하면서 state값을 업데이트할 수 있다! ( 전개구문연산자 안쓰고 직접 값을 변경할 수 있다!)

-> 비교하자면 기존 리덕스를 편하게 쓰기위해 추가했던 패키지들이 툴킷에는 아예 내장되어 있거나 더 획기적으로 리덕스를 편하게 쓸 수 있게 개선되었기 때문에 잘만 사용하면 기존 리덕스를 사용할 이유가 없다고 본다. 하지만 코드가 많이 생략되어있고 처음 리덕스를 접하는 사람이라면 기존 리덕스를 사용해보고 넘어오는 것이 좋다고 생각한다. 

😎리덕스 툴킷은 빌트인 타입스크립트이기 때문에 별도의 타입설치가 필요하지 않다고 하네요. 

리덕스 툴킷 API

  1. configureStore() : 리덕스 createStore함수와 비슷한 함수, slice reducer를 자동으로 합치고, 미들웨어를 추가할 수 있으며, redux-thunk를 기본적으로 제공한다. 또한, redux devtools Extension사용이 가능하다(크롬 리액트 개발자툴 확장 프로그램). 
  2. createReducer(): 리듀서 함수를 switch 구문으로 쓰기보다는 리듀서 함수를 계속 쓰는 lookup table방식을 쓸 수 있게해주고, immer라이브러리가 내장되어 있어서 변경가능한 코드를 작성할 수 있도록 해줍니다. e.g.)state.todos[3].completed = true; // 직접변경가능
  3. createAction(): 주어진 액션 타입 문자열로 액션 크리에이터 함수를 생성해줍니다. 함수 자체에 toString()이 정의되어 있어서 constant타입 대신 사용이 가능하다. 
  4. createSlice(): reducer 함수, slice이름, 초깃값을 넣을 수 있고 action creator와 action type을 가진 slice reducer를 자동으로 생성해준다. -> createSlice에 선언된 슬라이스 이름을 따라서 리듀서와 액션 생성자, 액션 타입이 자동 생성!
  5. createAsyncThunk: redux-thunk의 대체재 
  6. createEntityAdapter : 스토어에서 정규화된 데이터를 관리하기 위해 재사용 가능한 리듀서 및 selector 집합을 생성한다.
  7. createSelector : reselect 라이브러리의 유틸리티 기능과 똑같음

일반적인 앱은 configureStore, createSlice 만으로 구현이 가능하다. 밑줄 쳐놓은 API는 안써봐서 잘 모른다. 

 

리덕스 툴킷 세팅하기

1.1 리덕스 툴킷 설치하기

yarn add @reduxjs/toolkit react-redux
  • 리덕스 툴킷을 설치한다면 redux는 설치하지 않아도 됩니다. 기존에 깔아두셨다면 삭제하세요(react-redux랑 다름).  ->다시 개발 서버를 재가동해준다. (yarn start)

 

https://velog.io/@djaxornwkd12/Redux-Toolkit-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

 

Redux Toolkit 알아보기

오늘은 리덕스 툴킷에 대해 알아보자~! Redux Toolkit이란 사용이유 Redux Toolkit은 Redux를 더 사용하기 쉽게 만들기 위해 Redux에서 공식 제공하는 개발도구이다. Redux Toolkit은 아래와 같은 Redux의 문제점

velog.io

 

 

사용하려는 redux에 createSlice를 import합니다. 

import { createSlice } from "@reduxjs/toolkit";

전역 상태의 slice를 미리 만들어놔야 합니다. counter기능을하는 counterSlice.js입니다. 

 

counterSlice.js

//counterSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialCounterState = { counter: 0, showCounter: true };
//원래 얘는 모듈에 들어가는 애인데 적어서 여기에 쓴듯

//✅createSlice는 객체를 인자로서 생성합니다.
//initialState: initialState ->는 initialState 하나만 써도 js가 알아서 확장해서 알아봅니다.
//불변성을 유지할 필요없습니다. -> immer라는 패키지가 내부적으로 불변성을 유지시켜줍니다.
//state, action은 필요에 따라 사용 action에 붙은게 payload임
//🐱‍👤payload는 추가 데이터를 말한다. action.~ 뒤에 들어가는 애들임
//action은 payload를 가지고 있다. e.g.) action.payload


const counterSlice = createSlice({
  name: "counter",
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    reset(state) {
      state.counter = 0;
    },
    incrementByFive(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggle(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

//slice전체가 필요한 것이 아니라 reducer 가 필요하므로 reducer만 내보내준다.

export const counterActions = counterSlice.actions;
export default counterSlice.reducer;

슬라이스의 reducers에는 함수들이 들어간다! 각각의 함수는 인자로 state와 action을 가진다.

 

 

이제 액션을 어떻게 전달(dispatch)해야 할까? 
기존 리덕스라면 if문(switch문)을 통해 체크하고 각 액션에 어떤 식별자가 대응하는 지 알 수 있음

 

하지만 리덕스 툴킷의 경우, 
createSlice의 actions key 및 객체를 사용하면 액션크리에이터가 자동으로 액션 객체를 생성해준다.
->e.g.) counterSlice.actions.리듀서명
아래와 같은 객체를 리턴한다. 
{type: "자동으로 생성된 고유 식별자"} -> 즉, 이 객체는 우리가 type에 고유 식별자를 지정하지 않아도 이미 type 프로퍼티를 가지고 있음 (액션마다 다른 자동생성된 고유 식별자)-> 액션 식별자와 액션 객체를 생성할 필요가 없어짐 ^ ^

✅ 리덕스 툴킷의 추가 데이터를 가지고 있는 프로퍼티 명은 기본적으로 payload이다. //즉 우리가 타입명과 action에서 받아오는 추가 프로퍼티명을 작명할 필요가 없어진다.

+밑줄친 부분 추가 설명: 컴포넌트에서 dispatch를 통해서  
incrementByFive(state, action) { state.counter = state.counter + action.amount; }, 위의 예시에서 amount(우리가 작명함) -> payload(기본 리덕스 툴킷 추가 데이터 프로퍼티명) 로 통일
즉, amount/new_user/delete_num/등의 작명이 필요없어지고 payload로 통일해서 쓰면 된다.

 

 

 

store.js 

//store.js 

import { applyMiddleware, combineReducers, createStore } from "redux";
import { createSlice, configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counterSlice";
import authReducer from "./modules/authSlice";

//createStore에는 포인터가 있어야 한다. 리듀서 함수에서 매개변수로

// export const RESET = "reset";
// const counterReducer = (state = initialState, action) => {
//   if (action.type === "increment") {
//     return {
//       counter: state.counter + 1,
//       showCounter: state.showCounter,
//     };
//   }
//   if (action.type === "decrement") {
//     return {
//       //아래처럼 숫자를 넣어놓는 것은 하드코딩임
//       counter: state.counter - 1,
//       showCounter: state.showCounter,
//     };
//   }
//   if (action.type === "incrementFive") {
//     return {
//       //보통은 아래처럼 action에서 입력값을 받아와서 유동적으로 사용함
//       counter: state.counter + action.amount,
//       showCounter: state.showCounter,
//     };
//   }
//   //아래의 경우는 위와 반대로 counter의 state는 기존 것을 유지하고 showCounter만 !를 통해 false로 만들어준다.
//   if (action.type === "toggle") {
//     return {
//       counter: state.counter,
//       showCounter: !state.showCounter,
//     };
//   }
//   if (action.type === RESET) {
//     return {
//       counter: initialState.counter,
//       showCounter: state.showCounter,
//     };
//   }

//   return state;
// };

// const rootReducer = combineReducers({ counterReducer });
// const enhancer = applyMiddleware(...middlewares);
// const store = createStore(counterSlice.reducer);
//--------------여기까지 주석처리해놓은 것들이 기존 리덕스---------------------
//--------------아래부터는 툴킷적용-----------------------


//✅ 여러개의 reducer를 보낼 경우 store에 접근해서 데이터를 추출하는 방식이 좀 변하게 된다.
// useSelector에서 기존에 counter만 root reducer로 존재할 때는 useSelector((state) => state.counter);
//이렇게 값을 추출했지만 지금은 state => state.counter.counter로 접근해야 한다.
// counter.counter에서 1번째는 store의 reducer에 key값으로 들어가있는 counter을 가리키고
// 그 다음 counter은 해당 counter 슬라이스의 state인 counter를 가리킨다.
const store = configureStore({
  // reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
  reducer: { counter: counterReducer, auth: authReducer },
});

export default store;

리덕스 툴킷에서는 configureStore함수를 쓴다.
- configureStore(리덕스툴킷)는 createStore(리덕스)처럼 store를 만든다. => 다른 점은 여러 개의 리듀서를 하나의 리듀서로 쉽게 합칠 수 있다는 점이다. configureStore()에 객체를 전달한다. {}

 

✅ configureStore이 모든 리듀서를 하나의 큰 리듀서로 병합할 것임
객체를 인자로 받는데 그 안에 또 객체를 받는다. reducer가 여러 개일 경우 !
e.g.) const store = configureStore({
  reducer: {counter: counterSlice.reducer, auth: authSlice.reducer}
})


🙌slice가 하나이면 store의 주요리듀서로 할당한다.  -> 단일 리듀서면 {}와 key값이 필요없음
(하지만 우리 프로젝트에서 단일 슬라이스(슬라이스에 리듀서가 포함)일리가 없으니 이건 상식 정도로만 알아두자)
e.g.) reducer: counterSlice.reducer //단일 슬라이스일때 이렇게 작성할 수 있다. 

 

counter.js (컴포넌트)

//counter.js (컴포넌트)

import classes from "./Counter.module.css";
import { useSelector, useDispatch } from "react-redux";
// import { RESET } from "../redux/configureStore";
import { counterActions } from "../redux/modules/counterSlice";
//useDispatch함수는 Redux store에 대한 action을 보낸다.


const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter.counter);
  const show = useSelector((state) => state.counter.showCounter);
  // console.log(counter);
  //리덕스의 useSelector 훅을 사용해서 store의 값을 가져올 수 있다.

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };
  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };
  // amount: 5에 해당하는 것을 payload라고 부른다. 이걸 action에 추가함
  // action payload는 일반적이며 action객체에 추가할 수 있는 옵션이라고 생각하면 된다.
  const incrementByFiveHandler = () => {
    dispatch(counterActions.incrementByFive(5)); //툴킷 ver
    //✅{type: SOME_UNIQUE_IDENTIFIER, payload: 5} <- 이렇게 생성해줌 payload는 리덕스 툴킷에서 기본적으로
    // 사용하는 필드명이다. -> 저 이름도 우리가 지을 필요가 없는 것 ()함수의 인자로 넘겨주면 된다.
    //dispatch({type:"increaseByFive", amount: 5 }) 넘겨야 하는 payload가 있는 경우 리덕스ver
  };
  const toggle = () => {
    dispatch(counterActions.toggle());
  };
  const reset = () => {
    dispatch(counterActions.reset());
    console.log(counter);
    console.log(show);
  };

  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      {show ? (
        <div className={classes.value}>{counter}</div>
      ) : (
        <div className={classes.value}></div>
      )}
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={decrementHandler}>Decrement</button>
        <button onClick={incrementByFiveHandler}>Increment by 5</button>
      </div>
      <button onClick={toggle}>Toggle Counter</button>
      <button onClick={reset}>reset the counter</button>
    </main>
  );
};

export default Counter;

 

좀 더 자세한 비교분석을 원하시면 아래의 블로그를 참고해주세요. 

https://velog.io/@letthere/react-redux-toolkit-%EC%82%AC%EC%9A%A9-%EA%B0%80%EC%9D%B4%EB%93%9C

 

[react] Redux Toolkit 사용 가이드

redux-toolkit 공식 문서 사용 가이드 개인 학습용으로 작성한 글입니다. Redux Toolkit의 목표는 일반적인 Redux 사용 사례를 단순화하는 것입니다. Redux Toolkit이 Redux 관련 코드를 개선하는데 도움이 되

velog.io

 

+ 미들웨어에 관련된 부분은 추가 정리하겠습니다. 

 

비동기 논리 및 데이터 가져오기 

미들웨어를 사용하여 비동기 로직 사용하기

리덕스 Store는 비동기 로직에 대해 아무것도 모른다. 동기적으로 작업을 전달하고 루트 리듀서 함수를 호출하여 상태를 업데이트하고  UI에 변경 사항을 알리는 방법만 알고 있다. 그렇기 때문에 모든 비동기성은 스토어 외부에서 발생해야 한다. 

리덕스 미들웨어는 스토어를 확장하고 아래와 같은 것을 가능하게 해준다. 

  • 액션이 전달될 때 추가 로직 실행(e.g. 액션 및 상태 로깅)
  • 디스패치된 액션 일시중지, 수정, 지연, 교체 또는 중지
  • dispatch와 getState에 엑세스할 수 있는 추가 코드 작성
  • 실제 액션 객체를 디스패치하는 대신 함수 및 프로미스와 같은 일반 액션 객체 외에 다른 값을 수락하는 방법을 가르친다.

 

댓글