[React] Redux-Thunk

Jinny·2023년 11월 29일
1

React

목록 보기
16/23
post-custom-banner

1. Redux 미들웨어

기존 리덕스에서 액션 객체를 dispatch하면 액션이 리듀서에게 전달되고 전달된 값을 통해 스토어에 새로운 state를 반환한다.
액션 객체를 바로 리듀서에 전달되기 때문에 중간과정에서 우리가 하고싶은 작업을 할 수 없다.

업로드중..

Redux 미들웨어를 사용하면 이 과정 사이에 우리가 원하는 작업들을 넣을 수 있다.

예를 들어, counter 프로그램에서 특정 숫자를 더할 때 바로 값이 업데이트되는 것이 아니라 몇초 기다리는 작업을 미들웨어에서 수행할 수 있다.

➡️ 서버와의 통신할 때 주로 이용되며 대표적인 리덕스 미들웨어는 Redux-thunk가 있다.

1.1 redux-thunk

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

thunk를 사용하면 dispatch 시 액션 객체가 아닌 함수를 dispatch할 수 있게 된다.
➡️ 즉, 함수를 통해 원하는 동작을 실행할 수 있다.

1.2 5초 뒤에 카운터 증가하는 함수 만들기

  1. thunk 함수 만들기 (createAsyncThunk)
  2. extraThunk에 thunk 등록
  3. dispatch
  • 보통 thunk의 이름은 __로 시작한다.
  • thunk 함수의 인자는 이름 ,함수를 작성한다.
  • 함수에는 컴포넌트에서 보내줄 payload와 thunk 내장 기능인 thunkAPI 2개의 인자를 받는다.
  • thunkAPI안에 있는 dispatch를 통해 컴포넌트로부터 전달받은 payload를 전달한다.
    ❗ dispatch할 때 액션 creator를 전달해야한다!
    ➡️ 그래야 액션 객체로 변경되어 정상적으로 호출된다.

counter.js

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

const initialState = {
    number: 0
};

//thunk 함수 생성
export const __addNumber = createAsyncThunk(
    "ADD_NUMBER_WAIT", //이름
    (payload, thunkAPI) => { //함수
      //수행할 작업 작성  
      setTimeout(() => {
         //액션 creator 전달
         thunkAPI.dispatch(addNumber(payload));
        }, 5000);
    }
);


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 default counterSlice.reducer;
export const { addNumber, minusNumber } = counterSlice.actions;

요약

  • 리덕스 미들웨어를 사용하면 액션이 리듀서로 전달되기 전에 원하는 작업을 수행할 수 있다.
  • Thunk를 사용하면 객체가 아닌 함수를 dispatch할 수 있다.
  • 리덕스 툴킷에서 Thunk 함수를 생성할 때
    createAsyncThunk를 이용한다.
  • createAsyncThunk의 인자는 액션 value, 함수로 이루어져 있다.
  • 두 번째 인자 함수의 첫번째 인자는 컴포넌트에서 보내준 payload, 두번째 인자는 thunk에서 제공하는 여러 기능이다.

1.3 thunk에서 Promise 반환

  1. thunk 함수 구현
  2. 리듀서 로직 구현 (extraReducers)
    👉 isLoading, isError, data를 통해 제어
  3. 기능 확인(네트워크 탭) + devtools
  4. Store의 값을 조회 + 화면에 렌더링
  • thunk 함수는 서버랑 통신하는 함수이기 때문에 비동기 함수이어야 한다.
  • 서버와 클라이언트 간의 통신은 항상 성공을 보장할 수 없기에 요청이 성공하는 경우 실행되는 코드와 실패했을 때 실행되는 코드가 분리된 try-catch문을 사용한다.

todosSlice.js

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

//로딩, 에러 여부 데이터도 관리
const initialState = {
    todos: [],
    isLoading: false,
    isError: false,
    error: null,
};

export const __getTodos = createAsyncThunk(
    'getTodos',
    async (payload, thunkAPI) => {
        try {
            const response = await axios.get('http://localhost:4000/todos');
            console.log('response', response);
            //네트워크 요청 성공한 경우 dispatch 해주는 기능을 가진 API
            return thunkAPI.fulfillWithValue(response.data);
        } catch (error) {
            console.log('error', error);
            return thunkAPI.rejectWithValue(error);
        }

    }
);

export const todosSlice = createSlice({
    name: 'todos',
    initialState,
    reducers: {},
    extraReducers: {
         //진행 중인 경우 
        [__getTodos.pending]: (state, action) => {
            state.isLoading = true;
            state.isError = false;
        },
        //성공한 경우 
        [__getTodos.fulfilled]: (state, action) => {
            console.log('fullfilled : ', action);
            state.isLoading = false;
            state.isError = false;
            state.todos = action.payload;
        },
        //실패한 경우 
        [__getTodos.rejected]: (state, action) => {
            state.isLoading = false;
            state.isError = true;
            state.error = action.payload;
        }
    }
});

export default todosSlice.reducer;
  • 데이터를 가져올 때 로딩 중인지 에러가 발생했는지 알기 위해 초기값에 따로 추가한다.
  • thunkAPI.fulfillWithValuethunkAPI.rejectWithValue는 네트워크 요청/실패한 경우 dispath를 해주는 기능으로 각각 받은 데이터(response.data), 에러(error)를 인자로 갖는다. 반드시 리턴을 해줘야한다!

👉 성공/실패/진행 여부를 판단할 때 , extraReducers에 작성한다. 로딩/에러 여부 값을 지정한다.

APP 컴포넌트에서 store에 접근한 todos를 통해
isLoading,error 데이터 값도 접근할 수 있다.

export default function App() {
  const dispatch = useDispatch();
  //로딩 중, 에러 여부 데이터도 접근 가능
  const { isLoading, error, todos } = useSelector((state) => state.todos);
  
  useEffect(() => {
    dispatch(__getTodos());
  }, [])

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>

  return (
    <>
      <h1>
        Thunk로 실습하기
      </h1>

      <ul>
        {todos.map(todo => {
          return (<li key={todo.id}>
            {todo.id} : {todo.todo}
          </li>)
        })}
      </ul>
    </>
  );
}
  • 로딩 중일 경우, 로딩 중이라고 표시되며 아래 코드는 실행되지 않는다. 만약 요청이 성공되면 todos 데이터를 화면에 보여준다.

    ➡️ 옵셔널 체이닝 없이 isLoading,error가 있는지 판단한 다음 모두 false인 경우에만 todos 데이터를 보여줄 수 있다.

post-custom-banner

0개의 댓글