React

Thunk

차돌박이츄베릅 2023. 7. 6. 11:59

서버와의 통신을 위해 미들웨어 사용.
가장 많이 사용되는 리덕스 미들웨어는 Redux-thunk청크와 saga사가.

 

thunk를 사용하면 우리가 dispatch를 할때 객체가 아닌 함수를 dispatch 할 수 있게 해줍니다. 

즉, dispatch(객체) 가 아니라 dispatch(함수)를 할 수 있게 되는 것

dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)

 

구현 순서

  1. thunk 함수를 만들기: createAsyncThunk
    reduxToolkit 내장 API
  2. creatSlice의 extraReducer에 thunk 등록
  3. dispatch(thunk 함수) 하기
  4. 테스트

 

사용법

(src/redux/modules/counterSlice.js)

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

// 청크 함수  생성. 청크는 이름이 앞에 언더바 두개를 줌
// 2개의 INPUT: 
//   (1) 이름: 의미는 크게 없음. Action Value임 
//   (2) 함수(인자 2개에 payload와 thunkAPI): 우리가 하고 싶은 작업을 구현하면 됨
//     인자 payload에서는 이 thunk함수가 외부에서 사용되었을 때 넣은 값을 조회할 수 있음
//     인자 thunkAPI는 thnuk가 제공하는 여러가지 API 기능들이 담긴 객체
export const __addNumber = createAsyncThunk('ADD_NUMBER_WAIT', (payload, thunkAPI) => {
    // 수행하고 싶은 동작: 3초를 기다리게 할 예정
    setTimeout(() => {
        thunkAPI.dispatch(addNumber(payload));
    }, 3000);
});
export const __minusNumber = createAsyncThunk('MINUS_NUMBER_WAIT', (payload, thunkAPI) => {
    // 수행하고 싶은 동작: 3초를 기다리게 할 예정
    setTimeout(() => {
        thunkAPI.dispatch(minusNumber(payload));
    }, 3000);
});
...

 

(App.jsx)

thunk 함수를 dispatch 하기

...
// 청크
dispatch(__addNumber(number));
...

 

 

 


구현 예시:

json-server를 띄우고 

Thunk 함수를 통해서 API를 호출하고 

서버로부터 가져온 값을 Store에 dispatch 하는 기능

 

준비

json-server 설치 및 서버 가동 (db.json)

Slice로 todos 모듈 추가 구현

configStore에서도 리듀서를 추가

 

구현 순서

  1. thunk 함수 구현 → __ getTodos()
  2. 리듀서 로직 구현: 원래는 slice.js의 reducers에 구현했는데 이제는 extraReducers에서 한다
    • extraReducers 사용: reducers에서 바로구현되지 않는 기타 Reducer로직을 구현할 때 사용하는 기능입니다. 보통 thunk 함수를 사용할 때 extraReducers를 사용합니다.
    • [중요 🔥] 통신 진행중, 실패, 성공에 대한 케이스를 모두 상태로 관리하는 로직을 구현합니다.
      서버와의 통신은 100% 성공하는 것이 아닙니다. 서버와 통신을 실패했을때도 우리의 서비스가 어떻게 동작할지 우리는 구현해야 합니다. 또한 서버와의 통신은 ‘과정' 입니다. 그래서 서버와 통신을 진행하고 있는 ‘진행중' 상태일때 우리의 서비스가 어떻게 작동해야할지 마찬가지로 구현해야 합니다.
    • 지금까지의 redux state(todos, counter)
       앞으로의 state는 (isLoading, isError, data) 
  3. 기능확인: (network) + devTools 이용해서 작동 확인
  4. Store 값 조회하고, 화면에 렌더링 하기

 

(src/redux/modules/todosSlice.js)

import { createAsyncThunk } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

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

export const __getTodos = createAsyncThunk('getTodos', async (payload, thunkAPI) => {
    // 비동기 통신이 끝나고 나면 thunkAPI를 이용해서 원래는 reducers로 넘겨줬었는데
    // 이제는 thunkAPI.fulfillWithValue,     thunkAPI.rejectWithValue를 이용해서 extraReducers로 보내줄거

    // 항상 성공을 보장할 수 없기 떄문에 try catch로 묶어줌
    try {
        // 서버랑 통신하는 부분이기때문에 반드시 비동기함수여야 함. async await
        // axios.get() (함수)은 Promise를 반환합니다. 
        // 그래서 반환된  Promise의  fullfilled 또는 rejected된 것을 처리하기위해 async/await 을 추가
        const response = await axios.get('http://localhost:4001/todos');
        console.log('response', response);

        // toolkit에서 제공하는 API
        // Promise -> resolve된 경우(=네크워크 요청이 성공한 경우) -> dispatch해주는 기능을 가진 API
        return thunkAPI.fulfillWithValue(response.data);
        // [__getTodos.fulfilled]로 디스패치 됨
        // extraReducers에서 사용

        // 청크 완료됐을때 return을 해줘야만 extraReducer로 넘어감
    } catch (error) {
        console.log('error', error);

        // toolkit에서 제공하는 API
        return thunkAPI.rejectWithValue(error);
        // [__getTodos.rejected]로 디스패치 됨
    }
});

export const todosSlice = createSlice({
    name: 'todos',
    initialState,
    reducers: {},
    // 원래는 우리가 action creator를 만들고,
	// 리듀서에서 스위치문을 통해서 구현해줘야 하는 부분을 모두 자동으로 해줌
    extraReducers: {
        [__getTodos.pending]: (state, action) => {
            // 아직 진행중일 때
            state.isLoading = true;
            state.isError = false;
        },
        [__getTodos.fulfilled]: (state, action) => {
            // 성공
            // 리듀서이기 때문에 함수 모양을 가지게 되고, state와 action을 가지게 됨
            // console.log('fulfilled : ', action);
            state.isLoading = false;
            state.isError = false;
            state.todos = action.payload;
            // 6번줄 todos부분으로 들어감
            // 24번에서 서버에서 받아온 값을 인자로 넘겨줬으니까 action.payload로 들어옴
        },
        [__getTodos.rejected]: (state, action) => {
            // 에러
            state.isLoading = false;
            state.isError = true;
            state.error = action.payload;
        },
    },
});

export const {} = todosSlice.actions;
export default todosSlice.reducer;

 

(Main.jsx)

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

function Main() {
    const dispatch = useDispatch();

    const { isLoading, error, todos } = useSelector((state) => {
        return state.todos;
    });

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

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

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

    return (
        <>
            <div>Thunk App</div>
            {todos.map((todo) => {
                return <div key={todo.id}>{todo.title}</div>;
            })}
            {/* <Input />
      <TodoList isActive={true} />
      <TodoList isActive={false} /> */}
        </>
    );
}

export default Main;

 

 

 

 


(src/redux/modules/todosSlice.js) 기본 파일 준비

import { createSlice } from "@reduxjs/toolkit"

const initialState = {
    todos: [],
}

export const todosSlice = createSlice({
    name: "todos",
    initialState,
    reducers: {

    }
})

export const {} = todosSlice.actions;
export default todosSlice.reducer;

'React' 카테고리의 다른 글

React Query  (0) 2023.07.06
Custom Hooks  (0) 2023.07.06
Axios  (0) 2023.07.06
json-server  (0) 2023.07.06
Redux Devtools  (0) 2023.07.06