Redux 미들웨어 - thunk

lovjgb·2022년 8월 5일
0
post-custom-banner
  • 미들웨어?
    • 버튼을 클릭했을 때 바로 업데이트 된 state값을 반환해버리는데, 이때, 이 state값이 3초 후에 업데이트 되도록 하려면 미들웨어를 사용해야 한다.

thunk

  • thunk는 리덕스에서 많이 사용하고 있는 미들웨어 중 하나이다.
  • thunk 함수는 createAsyncThunk 라는 툴킷 API를 사용해서 생성한다.
  • dispatch(객체)로 리듀서에 보냈었지만, 미들웨어를 사용하면 dispatch(함수)를 할 수 있게 되고 그 함수 내에서 객체가 만들어 진 후 리듀서에 보내져서 업데이트 된다.

(1) thunk함수 사용법

  • reateAsyncThunk 라는 툴킷 API을 사용해서 생성한다.
// src/redux/modules/counterSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
//createAsyncThunk 추가 임포트

//...중략
//thunk------------------------------->↓ 추가
export const __addNumber = createAsyncThunk(
  "counter/__addNumber", // 첫번째 인자 : action value
  // 두번째 인자 : 함수
  (args, thunkAPI) => {
    setTimeout(() => {
      thunkAPI.dispatch(addNumber(args));
    }, 3000); 
// dispatch하기 전에 setTimeout을 통해 5초 후에 dispatch되면서 counterSlice가 실행되면서 globalState가 업데이트 된다.
  }
);
  • 위에서는 thunkAPI.dispatch를 사용했지만 추가적으로 getState도 있다.
    • getState: thunk 함수안에서 현재 리덕스 모듈의 state 값을 사용하고 싶을 때 사용

코드 예시

  • thunk함수를 사용하여 counter가 5초 후에 + - 되도록 구현해보았다.

  • 코드 상태는 아래와 같다.

//src/redux/modules/counter.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

const initialState = {
  number: 0,
};

export const __addNumber = createAsyncThunk("counter/__addNumber", (args, thunkAPI) => {
  setTimeout(() => {
    thunkAPI.dispatch(addNumber(args));
  }, 3000);
});

export const __minusNumber = createAsyncThunk("counter/__minusNumber", (args, thunkAPI) => {
  setTimeout(() => {
    thunkAPI.dispatch(minusNumber(args));
  }, 3000);
});

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    addNumber: (state, action) => {
      state.number = state.number + action.payload;
    },
    minusNumber: (state, action) => {
      state.number = state.number + action.payload;
    },
  },
});
export const { addNumber, minusNumber } = counterSlice.actions;
export default counterSlice.reducer;
//src/App.js
import React from "react";
import Router from "./shared/Router";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __addNumber, __minusNumber } from "./redux/modules/counter";

const App = () => {
  const dispatch = useDispatch();
  const [number, setNumber] = useState(0);
  const globalNumber = useSelector((state) => state.counter.number);

  const onChangeHandler = (e) => {
    const { value } = e.target;
    setNumber(+value);
  };

  const onClickAddNumberHandler = () => {
    dispatch(__addNumber(number));
  };
  const onClickMinusNumberHandler = () => {
    dispatch(__minusNumber(number));
  };

  return (
    <div>
      <div>{globalNumber}</div>
      <input type="number" onChange={onChangeHandler} />
      <button onClick={onClickAddNumberHandler}>더하기</button>
      <button onClick={onClickMinusNumberHandler}>빼기</button>
      <Router />
    </div>
  );
};
export default App;
//src/config/configStore.js
import { configureStore } from "@reduxjs/toolkit";
import counter from "../modules/counter";

const store = configureStore({
  reducer: {
    counter,
  },
});

export default store;

Thunk에서 promise 다루기

서버와의 통신을 상태관리 할때는 data, isLoading, error 로 관리한다.

(1) data

  • 아래 코드예시에서 datatodos이다.

(2) isLoading이란?

  • 서버에서 값을 가져오는 상태를 말한다.
  • 초기값: false
  • isLoading의 변화
    • false : 초기 상태
      ---> true : 서버 통신 중일 때 true가 된다. (값을 가져오는 상태)
      ---> false: 통신이 끝날 때 false로 바뀐다.

(3) error 란?

  • 서버와의 통신이 실패한 경우 return할 값

(4) 모듈의 globalState 초기값 내에 3가지를 넣어서 사용한다.

//src/modules/todos.js
const initialState = {
  todos: [], // data
  isLoading: false, // false : 초기 상태
  error: null, // 초기에는 error가 없어서 null로 지정한것
};

fulfillWithValue

  • promise가 resolve(서버요청이 성공했을 때 ) 글로벌 스테이트 업데이트 해주는 함수 (dispatch해주는 함수)
  • fulfillwithvalue(date.date) 자체가

코드 예시

  • 아래 예시는 todos라는 모듈을 만들어서 (1) 데이터를 불러오는, 즉, 통신 중(loading)에 화면에 띄워주고, (2) 데이터를 불러오다가 통신 에러(error)가 나는 경우 콘솔창에 error설명을 띄워주는 코드이다. ( 성공 시에는 todos의 state에서 isloading값만 true가 되도록 설정함 ; 리덕스에서 확인 가능하다. )
  • counter.js 모듈은 위에 있는 코드와 동일하다.(변동 없음)
//db.json
{
  "todos": [{ "id": 1, "title": "hello world!" }]
}
//src/modules/todos.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  todos: [],
  isLoading: false, // 서버에서 데이터를 가져올 때의 상태를 나타낼 값
  error: null, // 서버에서 데이터 가져오다가 실패했을 때의 에러메세지 값
};

export const __getTodos = createAsyncThunk("todos/getTodos", async (payload, thunkAPI) => {
  try {
    //try : 성공 시
    const data = await axios.get("http://localhost:3001/todos");
    console.log(data.data);
    return thunkAPI.fulfillWithValue(data.data);
  } catch (error) {
    // catch : 실패 시
    console.log(error);
    return thunkAPI.rejectWithValue(error);
  }
});

export const todosSlice = createSlice({
  name: "todos",
  initialState,
  reducers: {},
  extraReducers: {
    [__getTodos.pending]: (state) => {
      console.log("pending 상태", state);
      state.isLoading = true;
    },
    [__getTodos.fulfilled]: (state, action) => {
      console.log("fufilled 상태", state, action);
      state.isLoading = false;
      state.todos = action.payload;
    },
    [__getTodos.rejected]: (state, action) => {
      console.log("rejected 상태", state, action);
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

export const {} = todosSlice.actions;
export default todosSlice.reducer;
//src/App.js
import React from "react";
import Router from "./shared/Router";
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __addNumber, __minusNumber } from "./redux/modules/counter";
import { __getTodos } from "./redux/modules/todos";

const App = () => {
  const dispatch = useDispatch();
  const [number, setNumber] = useState(0);
  const { isLoading, error, todos } = useSelector((state) => state.todos);
  const globalNumber = useSelector((state) => state.counter.number);

  //useEffect는 위에 적어줍니다..
  useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);

  if (isLoading) {
    return <div>로딩중</div>;
  }

  if (error) {
    return <div>{error.message}</div>;
  }

  const onChangeHandler = (e) => {
    const { value } = e.target;
    setNumber(+value);
  };

  const onClickAddNumberHandler = () => {
    dispatch(__addNumber(number));
  };
  const onClickMinusNumberHandler = () => {
    dispatch(__minusNumber(number));
  };

  return (
    <div>
      {todos.map((todo) => {
        <div key={todo.id}>{todo.title}</div>;
      })}
      <div>{globalNumber}</div>
      <input type="number" onChange={onChangeHandler} />
      <button onClick={onClickAddNumberHandler}>더하기</button>
      <button onClick={onClickMinusNumberHandler}>빼기</button>
      <Router />
    </div>
  );
};
export default App;
  • 연결 중 pending

화면에는 로딩중 이라고 뜬다.

  • 연결 성공 시 fulfilled
  • 연결 실패 시 에러 rejected, 화면은 아래와 같다.


profile
lovjgb
post-custom-banner

0개의 댓글