가수면

[Redux] Redux ToolKit thunk 본문

React/라이브러리

[Redux] Redux ToolKit thunk

니비앙 2022. 12. 10. 14:23

thunk

미들 웨어 비동기 처리. dispatch 를 할 때 객체가 아닌 함수를 dispatch할 수 있게 해줌.

 

 

thunk 사용하기

간단한 예제)

// src/redux/modules/counterSlice.js

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


export const __addNumber = createAsyncThunk(
	// 첫번째 인자 : action value
  "addNumber", 
	// 두번째 인자 : 콜백함수 
  (payload, thunkAPI) => {
    setTimeout(() => {
      thunkAPI.dispatch(addNumber(payload));
    }, 3000);
  }
);

이후 App.js의 액션 크리에이터 addNumber를 __addNumber로 수정하면 적용 완료.

 

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

//createAsyncThunk는 비동기 작업을 처리하는 action을 만들어준다.
export const __getTodos = createAsyncThunk(
  "getTodos",
  async (payload, thunkAPI) => {
    // try / catch문을 사용해서 여기에서 실패했을 경우 에러를 콘솔로 찍어라.
    try {
      const data = await axios.get("http://localhost:3001/todos")
      console.log(data);
    } catch (error) {
      console.log(error);
    }
  }
)
// src/App.jsx

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

const App = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);

  return <div>App</div>;
};

export default App;

 

fullfillWithValue / rejectWithValue

fullfillWithValue - 네트워크 요청이 성공한 경우에 dispatch해주는 기능

rejectWithValue - 네트워크 요청이 실패한 경우 dispatch해주는 기능

 

앞의 __붙이는 것은 thunk 함수에 사용될 녀석이라는 일종의 약속이다.

//기본 형태

export const __changeTodos = createAsyncThunk(
  "CHANGE_TODO",
  async (todo, thunkApi) => {
    try {
      await axios.patch(`http://localhost:3001/todos/${todo.id}`, { state: !todo.state })
      return thunkApi.fulfillWithValue(todo)
    } catch (error) {
      return thunkApi.rejectWithValue(error)
    }
  }
)

 

extraReducer 추가

thunk함수는 reducer가 아니라 외부에서 작성된 것이기 때문에 extraReducer라는 메소드를 사용해야 된다. (switch 조건문이라고 보면 됨)

리덕스 툴 킷의 2.0 버전에서 객체 표기법이 제거될 예정이다.

'builder callback' 표기법을 사용하도록 하자.

const todoSlice = createSlice({
  name: "todos",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(__changeTodos.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(__changeTodos.fulfilled, (state, action) => {
        state.isLoading = false;
        state.todos = state.todos.map((todo) => (todo.id === action.payload.id ? { ...todo, state: !todo.state } : todo));
      })
      .addCase(__changeTodos.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload;
      })

 

 

심화

심화 (1)

await의 http://localhost:3001/todos/${todo.id}로 가보면 지정한 녀석의 데이터 가 있는 것을 확인할 수 있다.

뒤에 쉼표 붙이고 객체로 오는 { state: !todo.state }는 그 페이지 데이터의 state를 dispatch로 가져온 todo를 활용해 바꾸겠다는 얘기다. (결국 초기 API짤 때 지정했었던 내용이 들어가야 한다는 것.)

export const __changeTodos = createAsyncThunk(
  "CHANGE_TODO",
  async (todo, thunkApi) => {
    try {
      await axios.patch(`http://localhost:3001/todos/${todo.id}`, { state: !todo.state })
      return thunkApi.fulfillWithValue(todo)
    } catch (error) {
      return thunkApi.rejectWithValue(error)
    }
  }
)

 

심화(2)

export const __detailTodos = createAsyncThunk(
  "DETAIL_TODO",
  async (id, thunkApi) => {
    try {
      const detail = await axios.get(`http://localhost:3001/todos/${id}`)
      return thunkApi.fulfillWithValue(detail.data);
    } catch (error) {
      return thunkApi.rejectWithValue(error)
    }
  }
)

 

 

만일 위 코드를 fulfillWithValue(detail)로 바꿀 경우 action과 state는 직렬화할 수 없는 데이터를 받지 않는다며 오류가 뜬다. (a non-serializable value was detected in an action...)

먼저 직렬화라는 개념을 알아보자.

 

컴퓨터 메모리 상에 존재하는 객체(Object) -> 문자열(string) 로 변환하는 것
= 직렬화(Serialization)

 

즉, 우리는 아래처럼 바꿀 수 있는 데이터를 action으로 넘겨줘야만 한다는 얘기다.

{
	"name" : "Alice",
	"age" : 27,
	"gender" : "male",
	"location" : "Incheon",
	"marriage" : false,
	"friends" : ["Lee","Shin","Jang"]
}

이는 로컬 스토리지에서 setItem을 JSON.stringify()로 바꾸고  getItem을 해줄 때 JSON.parse()로 바꿔주는 것과 같은 원리로 보인다.

export const getLocal = () => {
  return JSON.parse(localStorage.getItem('todolist'))
}
export const setLocal = (todo) => {
  localStorage.setItem('todolist', JSON.stringify(todo))
}

 

따라서 fulfillWithValue의 값을 const detail안에 있는 data를 지정해 넘겨줘야만 한다.

'React > 라이브러리' 카테고리의 다른 글

[Styled Components] Styled Components 심화  (0) 2022.12.19
[Styled Components] Styled Components Global Style  (0) 2022.12.17
[Redux] Redux ToolKit  (0) 2022.12.09
Router  (0) 2022.12.03
[Styled Components] Styled Components  (0) 2022.12.02
Comments