51일차 TIL : Thunk

변시윤·2022년 12월 20일
0

내일배움캠프 4기

목록 보기
52/131

학습내용

  • Thunk 함수
  • Thunk에서 Promise 다루기

Thunk 함수

Middleware

액션이 리듀서로 전달되기 전에 다른 작업을 추가해주는 작업. 대부분 서버와의 통신을 위해 사용하며 Redux-thunk 역시 미들웨어 중 하나다.

Thunk는 객체가 아닌 함수를 Dispatch하는데 이것을 Thunk 함수라고 한다. thunk 함수는 createAsyncThunk라는 API를 사용해서 생성한다.

redux/modules/manageTodo.js

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

export const __addTodo = createAsyncThunk(
  "addTodo",
  (payload, thunkAPI) => { 
    setTimeout(() => {
      thunkAPI.dispatch(addTodo(payload));
    }, 1000);
  }
);
  • 첫 번째 인자 : Action Value "addTodo"
  • 두 번째 인자 : 함수 (payload, thunkAPI) => {...}
    • payload : 컴포넌트에서 보내주는 payload
    • thunkAPI : thunk에서 제공하는 여러 기능
    • {...} : 리듀서로 액션을 전달하기 전에 실행할 작업

Header.jsx

 import { __addTodo } from "../../redux/modules/manageTodo";
 
 const Header = () => {
  .
  .
  .
  const addBtn = () => {
    if (!date) {
      alert("빠진 내용이 없나 확인해보세요.");
      focusDate.current.focus();
    } else if (!todo) {
      alert("빠진 내용이 없나 확인해보세요.");
      focusTodo.current.focus();
    } else {
      setDate("");
      setTodo("");
      dispatch(
        __addTodo({
          id: uuid(),
          date,
          todo,
          isDone: false,
        })
      );
      focusDate.current.focus();
    }
  };
  .
  .
  .
 }

dispatch(함수) → 함수 실행 → 함수 내 dispatch(객체)


Thunk에서 Promise 다루기

json-server를 띄운 후 Thunk 함수를 통해서 API를 호출하고 서버로부터 가져온 값을 스토어에 dispatch 해보자.

데이터 조회 후 스토어로 dispatch

redux/modules/manageTodo.js

import axios from "axios";

const initialState = {
  initialList: [],
  isLoading: false,
  error: null,
};

서버와의 통신에서는 대부분 data, isLoading, error로 상태를 관리한다.

  • isLoading
    서버 데이터 조회 상태를 나타내는 값. false인 상태에서 서버와 통신이 시작되면 true, 통신이 성공/실패하면 false로 변경된다.
  • error
    서버와 통신을 실패하는 경우 서버에서 보내는 에러 메시지를 담는 값. 초기에는 에러가 없으므로 초기값은 null
export const __getTodos = createAsyncThunk(
  "getTodos",
  async (payload, thunkAPI) => {
    try {
      const data = await axios.get("http://localhost:3001/todos");
      return thunkAPI.fulfillWithValue(data.data);
    } catch (error) {
      return thunkAPI.rejectWithValue(error);
    }
  }
); 

fulfillWithValuerejectWithValue는 모두 툴킷에서 제공하는 API다.

-fullfillWithValuerejectWithValue
네트워크 요청성공실패
매개변수payload정해지지는 않았지만 네트워크 요청 실패시에 가져오는 값이므로 주로 error를 사용

extraReducers

const manageTodo = createSlice({
    .
    .
    .
    extraReducers: {
    [__getTodos.pending]: (state) => {
      state.isLoading = true;
    },
    [__getTodos.fulfilled]: (state, action) => {
      state.isLoading = false;
      state.initialList = action.payload;
    },
    [__getTodos.rejected]: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});
  • pending
    네트워크 요청 시작

  • fulfilled
    네트워크 요청 성공. 서버에서 가져온 데이터 반환.

  • rejected
    네트워크 요청 실패. catch문의 error 객체 반환.

Map Object 표기법
상대적으로 간결한 표기법이나 자바스크립트에서만 유효하기 때문에 Builder Callback 표기법을 권장함

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

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

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

Builder Callback 표기법
타입스크립트와의 호환성면에서 유리

➀ 각 라인마다 빌더 메서드를 나누어 호출

const counterReducer = createReducer(initialState, (builder) => {
  builder.addCase(increment, (state) => {})
  builder.addCase(decrement, (state) => {})
})

➁ chaining 형태

const counterReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state) => {})
    .addCase(decrement, (state) => {})
})

렌더링

Todolist.jsx

import { useDispatch, useSelector } from "react-redux";
import { __getTodos } from "../../redux/modules/manageTodo";
import { useEffect } from "react";

const Todolist = () => {
  const { isLoading, error, initialList } = useSelector(
    (state) => state.manageTodo
  );
  
    useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);
  .
  .
  .
  if (isLoading) {
    return <>Loading...</>;
  }
  if (error) {
    return <>{error.message}</>;
  }
  return(
  <>
  	{initialList.map((item, i) => {...}
  </>
  );
}

useSelector로 store를 조회해서 화면에 렌더링하는 방식은 동일하나 상태에 따라 조건부 렌더링을 해야 한다.


📌 참조문헌

profile
개그우먼(개발을 그은성으로 하는 우먼)

0개의 댓글