⚛︎ React Toolkit

zooyaho·2022년 8월 22일
0
post-thumbnail

⚛️ React Toolkit

  • 리덕스의 특징 몇 가지 특징을 단순화 함.
  • 훨씬 짧고 간결하며, 유지보수가 쉬워 짐!

📎 패키지 설치

npm install @reduxjs/toolkit

  • 패키지에 redux라이브러리가 포함되어있기 때문에, redux라이브러리를 별도로 설치하지 않아도 됨.

📎 store폴더 생성

  • slice에 따라 파일을 생성하여 관리한다. ex) counter-slice.js, auth.js등...

💡 함수

import {...} from '@reduxjs/toolkit'

  • createSlice({name: '', initialState: '', reducers: {}})
    • 객체를 인수로 받음
    • 전역상태의 slice를 미리 만들어 놓음.
    • 서로 직접적인 관계가 아닌 상태가 여러개로 나눠져 있음.
    • name : 상태마다 식별자가 필요하기 때문에 모든 slice는 이름이 있어야함.
    • initialState : 상태마다 초기상태 설정
    • reducers : 객체 혹은 맵으로 리듀서 설정, reduders 에 정의한 메서드들은 자동으로 첫번째인수로 state 최근값이 들어오고, 두번째 인수로 action이 들어옴!, action에 따라 자동으로 메서드가 리덕스에 의해 호출 됨.
  • configureStore({reducer: ...})
    • createStore()처럼 store를 만듬. 다른 점은 모든 리듀서를 하나의 큰 리듀서로 병합함!! 스토어는 루트 리듀서 하나만 가지고 있음!
    • 인수로 설정객체가 들어오며, 리듀서 프로퍼티를 정함.
    • 리듀서 프로퍼티에 전역 상태를 담당하는 주요 리듀서로서 사용할 수 있음.
    • reducer가 한개일 경우 : ex) configureStore({reducer: counterSlice.reducer})
    • reducer가 여러개일 경우 : reducer map을 생성하여 리듀서마다 key값을 설정한다. 나중에 하나의 주요 리듀서로 자동 합쳐지고, 주요 리듀서를 스토어에 노출한다. ex) configureStore({reducer :{ counter: counterSlice.reducer, auth: authSlice.reducer }})
  • 리듀서Slice.actions
    - 리듀서Slice.actions가 reduders객체를 반환 함!
    • actions객체의 key로 해당 리듀서 메서드를 사용할 수 있음.
    • type프러퍼티를 가지고 있는 해당 액션 객체를 반환
    • 그러므로 이런 메서드key를 액션 생성자라고 부름.

✔️ 예제

👾 store/index.js - toolkit사용 전

const counterReducer = (state = initialState, action) => {
  if (action.type === "increment") {
    return {
      counter: state.counter + action.amount,
      showCounter: state.showCounter,
    };
  }
  
  if (action.type === "decrement") {
    return {
      counter: state.counter - action.amount,
      showCounter: state.showCounter,
    };
  }

  if (action.type === "toggle") {
    return {
      showCounter: !state.showCounter,
      counter: state.counter,
    };
  }

  return state;
};

const store = createStore(counterReducer);

export default store;

👾 store/index.js - toolkit 사용

import { createSlice } from '@reduxjs/toolkit'

const initialState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: 'counter', // 식별자 설정
  initialState: initialState, // 초기상태 설정
  reduders: { // 리듀서 설정
    increment(state, action) {
      state.counter = state.counter + action.payload;
    },
    decrement(state, action) {
      state.counter = state.counter - action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    }
  }
});
// action에 따라 자동으로 메서드가 리덕스에 의해 호출 됨.

// 스토어는 루트 리듀서 하나만 가지고 있음!
const store = configureStore({reducer: counterSlice.reducer});

// 액션 식별자 값을 얻으려면 counterSlice.actions 사용
// 액션이 필요한 컴포넌트에서 사용 가능하게 export 함!
export const counterActions = counterSlice.actions;
// counterSlice.actions가 reduders객체를 반환 함!

export default store;

🔥 increment(state, action) { state.counter = state.counter + action.amount; },
👉🏻 여전히 원래 있는 상태를 변경할 수 없지만, React Toolkit에서 제공하는 createSlice를 사용하면 기존상태를 직접적으로 변경할 수 있음.
👉🏻 왜냐하면 React Toolkit 내부적으로 immer라는 다른 패키지를 사용하는데 이런 코드를 감지하고 자동으로 원래 있는 상태를 복제하고 새로운 상태 객체를 생성하여 오버라이드를 하기 때문이다.
👉🏻 createSlice()를 사용하면 불변성을 고려하지 않고 직접 상태 업데이트할 수 있다. -> 내부적으로 알아서 변경할 수 없는 코드로 변경함.

👾 Counter.js

import { useSelector, useDispatch } from "react-redux";
import { counterActions } from '../store/index';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

  const incrementHandler = () => {
    // action객체가 자동 생성 후 increment메서드 호출
    // payload가 있을 경우 인자로 전달
    dispatch(counterActions.increment(10));
    // Toolkit이 { type: SOME_UNIQUE_IDNETIFIER, payload: 10}을 생성하여 자동으로 전달!!
    // 이때 payload는 기본값으로 사용하는 필드임!!
  };
  
  const decrementHandler = () => {
    dispatch(counterActions.decrement(10));
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
  };

  return (
    <main>
      <h1>Redux Counter</h1>
      {show && <div>{counter}</div>}
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={increaseHandler}>Increase by 10</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};

export default Counter;

● middleware

  • 액션이 dispatch되어서 리듀서에서 이를 처리하기전에 사전에 지정된 작업들을 설정한다.
  • 액션과 리듀서의 중간자, 그래서 액션을 제어할 수 있음.
  • 비동기 작업을 수행하고 중간에 작업을 캐치하고 작업을 수행한다.

applyMiddleware

  • 미들웨어는 store 생성할 때 설정함. redux 모듈 안에 들어있는 applyMiddleware 를 사용하여 설정 할 수 있다.
  • next(action): action을 받기 때문에 다른 action객체를 보낼 수 있음.
  • next()를 호출하지 않으면 리듀서 함수를 호출하지 않음.
import { applyMiddleware } from 'redux';

const loggerMiddleware = (store) => (next) => (action) => {
      // 액션을 다음 미들웨어로 넘김
  	  // 더 이상 미들웨어가 없으면 리듀서에 전송
      const result = next(action);
	  // 스토어 상태 기록
  	  console.log(store.getState());
      return result;
      // 여기서 반환하는 값은 store.dispatch(ACTION_TYPE) 했을때의 결과로 설정됨
      // next(action) = {type: "AGE_UP", value:1}
}

// 미들웨어가 여러개인경우에는 파라미터로 여러개를 전달해주면 된다. 예: applyMiddleware(a,b,c)
const store = configureStore(
  {reducer: counterSlice.reducer},
  applyMiddleware(loggerMiddleware)
);

● Thunk를 이용해 비동기 작업을 처리하는 방법

📎 Thunk

  • Redux에 함수를 dispatch하면 reducer로 전달하지 않고(= next를 호출하지 않고) 해당 함수를 실행시켜주는 간단한 redux middleware
  • 보통 비동기 api request를 보내고, reponse를 액션에 담아 dispatch하는 패턴으로 작성
  • 다른 작업이 완료될 때까지 작업을 지연시키는 함수이다.
  • 작업을 반환하는 다른 함수를 반환한다...
  • toolkit은 Thunk 기반으로 작성이 되어있음.

📎 createAsyncThunk

  • 비동기 작업을 처리하는 action을 만들어 준다!
  • action creator 생성
  • createAsyncThunk('type', async ()=>{})
    • 첫번째 인자: action creator이기 때문에 type을 지정
    • 두번째 인자: action이 실행됐을 때 처리되어야하는 작업

✔️ 예제

: 버튼 클릭 시 api호출하여 data를 store에 저장

🔴 thunk사용 전

<button onClick={async ()=>{
  const response = await fetch('https://...');
  const data = response.json();
  dispatch(set(data.value));
  // dispatch({type:'counterSlice/set', payload: data.value});
}}>async fetch</button>

👉🏻 api호출이 중복 될 경우, 코드양이 길어지고 가독성이 떨어짐

🔵 thunk사용 후

👾 App.js

import {asyncUpFetch} from './counterSlice';
...

<button onClick={()=>{
  dispatch(asyncUpFetch());
}}>async thunk</button>

👉🏻 return data.value 반환된 값이 자동으로 value에 저장됨!!

👾 CounterSlice.js

const asyncUpFetch = createAsyncThunk(
  'counterSlice/asyncUpFetch', // action 타입 지정
  async () => {
    const resp = await fetch('htttps://...');
    const data = resp.json();
    return data.value;
  }
)

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: { value:0, status: 'welcome'},
  // 동기적인 작업은 reducers에서 작업
  reducers: {
    up: (state, action) => {
      state.value = state.value + action.payload;
    }
  },
  // 비동기적인 작업은 extraReducers에서 작업
  extraReducers: (builder) => {
    // pending일때 실행할 reduser를 두번째 파라미터에 작성
    builder.addCase(asyncUpfetch.pending, (state,action)=>{
      state.status = 'Loading';
    })
    builder.addCase(asyncUpfetch.fulfilled, (state,action)=>{
      state.status = 'Complete';
    })
    builder.addCase(asyncUpfetch.rejected, (state,action)=>{
      state.status = 'Fail';
    })
  }
})

👉🏻 동기적인 작업은 reducers에서 작업하며, 자동으로 type이 생성됨!
👉🏻 비동기적인 작업은 extraReducers에서 작업하며, 자동으로 type이 생성되지 않음!

● DevTools 탐색하기

: 리덕스와 리덕스 상태를 좀 더 쉽게 디버깅할 수 있음.
: 많은 리덕스 상태가 여러 다른 슬라이스로 처리되고 다양한 작업이 진행되는 복잡한 애플리케이션에서는 작업 등의 디버그 상태에서 오류를 찾기 어려울 수 있음.
: 전체 리덕스 스토어의 현재 상태를 살펴볼 수 있음.
: 브라우저 확장으로 사용 및 설치가 가능함.
: Redux toolkit 사용 시 즉시 사용가능.
: Redux toolkit없이 사용하려면 추가코드를 설정해야함.

profile
즐겁게 개발하자 쥬야호👻

0개의 댓글