๐Ÿ“• ์žฌ๋ฏธ์žˆ๋Š” Redux(2)(redux-toolkit ์‚ฌ์šฉํ•˜๊ธฐ)

Lee Jooamยท2022๋…„ 5์›” 26์ผ
0

๋ฆฌ๋•์Šค๋Š” ์žฌ๋ฏธ์—†์—ˆ์ง€๋งŒ ๋ฆฌ๋•์Šค ํˆดํ‚ท ๋•๋ถ„์— ์กฐ๊ธˆ ์žฌ๋ฐŒ์–ด์กŒ๋‹ค. ์ •๋ง ๋‹คํ–‰์ด๋‹ค. ๐Ÿ˜€

๐Ÿ”จ ์„ค์น˜

npm install @reduxjs/toolkit react-redux

๋˜๋Š”

yarn add @reduxjs/toolkit react-redux

์„ค์น˜๋ถ€ํ„ฐ ๊ฐ„ํŽธํ•˜๋‹ค. ๊ณผ๊ฑฐ์—” redux, react-redux ๋˜ ํ•„์š”ํ•˜๋‹ค๋ฉด(๊ฑฐ์˜ ํ•„์ˆ˜์ ) devtools, saga, thunk, actions ๋“ฑ๋“ฑ ๋‹ค์–‘ํ•œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

์ฒ˜์Œ ์‚ฌ์šฉํ•ด๋ณธ๋‹ค๋ฉด ํ•„ํžˆ ์–ด์ง€๋Ÿฌ์›€์„ ๋Š๋‚€๋‹ค.

์ด๋Ÿฐ ์ž‘์—…๋“ค์ด ๋ชจ๋‘ ๊ฐ„ํŽธํ•ด์กŒ๋‹ค๋‹ˆ ์ •๋ง ๊ฐ์‚ฌํ•˜๋‹ค.

๐Ÿ™‰ ์‚ฌ์šฉ๋ฒ•

๋ฆฌ๋•์Šค ํˆดํ‚ท์€ ๋‚ด๋ถ€์ ์œผ๋กœ thunk์™€ devtools๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

1. store ์„ ์–ธ

// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
});

๊ธฐ์กด์˜ ์‚ฌ์šฉ๋ฒ•๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ store๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. ์ด๋•Œ createStore๊ฐ€ ์•„๋‹Œ configureStore๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ ์‚ฌ์šฉ๋ฒ•์ด ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.

๊ธฐ์กด์—๋Š” combineReducers๋กœ ํ†ตํ•ฉํ•ด ํ•˜๋‚˜์˜ reducer๋งŒ ์ œ๊ณตํ–ˆ๋‹ค๋ฉด ์ด์ œ๋Š” reducer ๊ฐ์ฒด์— ๊ทธ๋ƒฅ ์ถ”๊ฐ€ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋ณธ์ ์œผ๋กœ devtools๋ฅผ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋‹ค.

2. createSlice

// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

๊ธฐ์กด์—๋Š” actionCreator, reducer, initialState๋ฅผ ๊ฐ๊ฐ ๋”ฐ๋กœ ์„ ์–ธํ–ˆ์ง€๋งŒ createSlice๋Š” ํ•œ๋ฒˆ์— ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

name์€ action์˜ prefix ์—ญํ• ์„ ํ•œ๋‹ค.

ํ•˜๋‹จ์— ๋ณด์ด๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด counterSlice ๊ฐ์ฒด์˜ actions๋กœ action Creator๋ฅผ ์ฐธ์กฐํ•˜๊ณ , reducer๋กœ reducer๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ด€๋ จ์žˆ๋Š” ๊ฒƒ๋“ค์„ ํ•˜๋‚˜์˜ ๋ฌธ์„œ์—์„œ ๋‹ค๋ฃจ๋Š” Ducks ํŒจํ„ด์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

3. ์‚ฌ์šฉ

์‚ฌ์šฉ์€ ๊ธฐ์กด๊ณผ ๊ฐ™๋‹ค. useSelector๋ฅผ ์ด์šฉํ•ด ๊ตฌ๋…ํ•˜๊ณ , useDispatch ํ›…์„ ์ด์šฉํ•ด ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

4. ๋น„๋™๊ธฐ ์ž‘์—…

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์•„์ฃผ ํŽธ๋ฆฌํ•ด์กŒ๋‹ค. saga์™€ thunk๋ฅผ ํ†ตํ•œ ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด์„œ ๊ต‰์žฅํžˆ ์–ด๋ ค์›€์„ ๋Š๊ผˆ์—ˆ๋Š”๋ฐ, ๊ทธ์— ๋น„ํ•ด์„œ๋Š” ์•„์ฃผ ํŽธํ•ด์กŒ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

export const login = createAsyncThunk(
  "auth/login",
  async ({ username, password }) => {
    const response = await authAPI.login({ username, password });

    return response.data;
  }
);

const authSlice = createSlice({
  name: "auth",
  initialState: { user: null, error: null, loading: false },
  reducers: {
    logout: (state) => {
      state.user = null;
    },
  },
  extraReducers: {
    [login.pending]: (state) => {
      state.loading = true;
    },
    [login.fulfilled]: (state, action) => {
      state.loading = false;
      state.user = action.payload;
    },
    [login.rejected]: (state) => {
      state.loading = false;
      state.error = "error!!!";
    },
  },
});

createAsyncThunk๋Š” thuckActionCreator๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด๊ฒƒ์€ ์œ„์˜ ์˜ˆ์‹œ์—์„œ ๋ณด์ด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ pending, fulfilled, rejected์— ๋”ฐ๋ผ ๋‹ค๋ฅธ action๋“ค์„ ์ฐธ์กฐํ•˜๋Š” plain object๋‹ค.

auth/login/pending ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์•ก์…˜์„ ๋งŒ๋“ค์–ด ์ค€๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

dispatch๋กœ thnkAction์„ ํ˜ธ์ถœํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. pending ์•ก์…˜ ๋ฐœ์ƒ
  2. payloadCreator(createAsyncThunk์—์„œ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋“ค์–ด๊ฐ„ ์ฝœ๋ฐฑ) ํ˜ธ์ถœ
  3. Promise์˜ ๊ฒฐ๊ณผ(resolve, reject)์— ๋”ฐ๋ผ ๊ฐ๊ฐ์˜ ์•ก์…˜ ์‹คํ–‰

์ž‘์—…์ด ์•„์ฃผ ๊ฐ„ํŽธํ•ด์กŒ๋‹ค. ์ด๋ž˜์„œ ์ถ”์ƒํ™” ๊ณ„์ธต์„ ์ œ๊ณตํ•˜๋Š” ๊ฑธ ์—ฌ๋Ÿฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋ฒ•์—์„œ ๊ฐ•์กฐํ•˜๋Š” ๊ฒƒ์ผ์ง€๋„ ๋ชจ๋ฅด๊ฒ ๋‹ค.

5. ์—๋Ÿฌ ํ•ธ๋“ค๋ง

export const loginUser = createAsyncThunk(
  "user/loginUser",
  async ({ id, password }, { rejectWithValue }) => {
    try {
      const response = await authAPI.login({ id, password });
      return response.data;
    } catch (e) {
      return rejectWithValue(e.message);
    }
  }
);

// ์ƒ๋žต
  extraReducers: {
    [actions.loginUser.pending]: (state) => {
      state.isLoading = true;
    },
    [actions.loginUser.fulfilled]: (state, { payload }) => {
      state.name = payload.name;
      state.id = payload.id;
      state.isLogin = true;
      state.isLoading = false;
    },
    [actions.loginUser.rejected]: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  }

payload creator๋Š” ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ thunkAPI๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ thunkAPI.rejectWithValue๋ฅผ ์ด์šฉํ•˜๋ฉด rejected ์•ก์…˜์—์„œ payload๋กœ ์ง์ ‘ ์—๋Ÿฌ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

thunkAPI.rejectWithValue๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์ผ๋ฐ˜์ ์ธ ์—๋Ÿฌ๋ฅผ rejected ์•ก์…˜์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.

ํ›„๊ธฐ

๋ฆฌ๋•์Šค ํˆดํ‚ท์€ ์ฐธ ๊ฐ์‚ฌํ•œ ํŒจํ‚ค์ง€๋‹ค.

์•„์ง์€ ์ˆ™๋ จ๋„๊ฐ€ ๋‚ฎ์•„ ๋Šฅ์ˆ˜๋Šฅ๋ž€ํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜๋Š” ์—†๊ฒ ์ง€๋งŒ ๊ธฐ์กด์˜ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜๊ฑฐ๋‚˜ ๋‹ค์–‘ํ•œ ์˜ˆ์ œ๋ฅผ ์ ‘ํ•ด๋ณด๋ฉฐ ์ˆ™๋ จ๋„๋ฅผ ์Œ“์•„์•ผ๊ฒ ๋‹ค.

๋ฆฌ๋•์Šค๊ฐ€ ์กฐ๊ธˆ์€ ์ข‹์•„์งˆ ๊ฒƒ ๊ฐ™๋‹ค.

createAsyncThunk๋Š” ํ•ญ์ƒ ์ดํ–‰๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๋ง์ด ์žˆ๋Š”๋ฐ ์ด ๋ถ€๋ถ„์€ ์กฐ๊ธˆ ๋” ์•Œ์•„๋ด์•ผ๊ฒ ๋‹ค.

profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ๊ฑธ์–ด๊ฐ€๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€