반응형

 

들어가기 전에

 

이 포스팅 작성 시점 기준 패키지 버전 @reduxjs/toolkit v1.6.2, react-redux v7.2.6, axios v0.24.0입니다.

 

 

Redux Toolkit에 대해서

 

이 포스팅은 보기 전에 Redux Toolkit 기본 사용법에 대해 다룬 이전 포스팅을 보고 오시면 좋습니다.

 

 

axios에 대해서

 

axios는 ajax를 쉬운 코드로 사용할 수 있도록 편의성을 제공해주는 라이브러리입니다.

axios는 axios() 함수를 호출하는 것으로 ajax request를 보낼 수 있으며 리턴 값이 promise이기 때문에 then, catch, finally와 같은 메소드로 프로미스 체이닝을 통해 response 후속 조치를 할 수 있습니다.

 

* axios 프로미스 객체 사용법

axios({
  method,
  url,
  data,
}).then(response => {
  console.log(response);
}).catch(error => {
  console.log(error);
});

 

 

프로젝트 세팅

 

리액트 프로젝트를 CRA등으로 간편하게 세팅을 하고, redux-toolkit과 react-redux, axios를 설치해줘야 합니다.

 

npx create-react-app redux-axios-tutorial
cd redux-axios-tutorial
yarn add @reduxjs/toolkit react-redux axios

 

그리고 코드를 테스트하기 위해 axios로 통신할 API 서버가 있어야 합니다. 따로 구현하기 귀찮으니까 무료로 사용할 수 있는 API 서버로 테스트해보겠습니다. https://dummy.restapiexample.com/

 

 

 

Redux에서 Redux Toolkit과 axios를 사용하여 비동기 통신하기

 

리덕스 툴킷은 createAsyncThunk라는 함수를 제공해주는데 이 함수는 내부적으로 redux-thunk 라이브러리를 사용하고 있습니다. 그래서 redux-thunk를 사용해본 경험이 있다면 사용법이 익숙할 것 같습니다.

우선 createAsyncThunk는 createAction처럼 액션 생성 함수이기 때문에 첫 번째 파라미터로 액션명을 받습니다. 그리고 두 번째 파라미터로 콜백 함수를 작성하여 비동기 로직을 사용합니다.

 

* createAsyncThunk 기본 사용법

const getEmployees = createAsyncThunk("GET/EMPLOYEES", async () => {
  // ajax 요청하고 promise 객체를 리턴 받는 함수를 여기에 사용합니다.
}),

 

저 비동기 로직이 들어갈 자리에 axios를 사용할 것입니다.

 

const getEmployees = createAsyncThunk("GET/EMPLOYEES", async () => {
  return axios({
    method: "get",
    url: "http://dummy.restapiexample.com/api/v1/employees"
  });
}),

 

이제 이것을 리듀서와 엮어서 사용해보겠습니다.

 

* src/store/employees.js

export const action = {
  getEmployees: createAsyncThunk("GET/EMPLOYEES", async () => {
    return axios({
      method: "get",
      url: "http://dummy.restapiexample.com/api/v1/employees"
    }).then(response => response.data);
  }),
};

const initialState = {
  employees: [],
};

export const reducer = {
  getEmployees: (state, action) => {
    state.getEmployees = action.payload.data;
  }
};

const employeesReducer = createReducer(initialState, builder => {
  builder
    .addCase(action.getEmployees.fulfilled, reducer.getEmployees)
});

export default employeesReducer;

 

리듀서를 사용하는 스토어는 다음과 같습니다.

 

* src/store/index.js

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

import employeesReducer from "src/store/employees";

export const store = configureStore({
  reducer: {
    employees: employeesReducer,
  },
});

 

여기까지 리덕스 구현은 완료했습니다. 지금까지 작성한 로직을 flux패턴을 따라 잠시 점검해보겠습니다.

 

"GET/EMPLOYEES"라는 액션이 디스패치가 되면 액션 생성 함수인 createAsyncThunk의 콜백이 실행되면서 axios함수가 호출됩니다.

axios함수는 API 서버에 get method로 요청을 보내고 그 결과를 프로미스로 반환받습니다. 응답을 성공적으로 받으면 then 메소드 내에서 response.data를 리턴하여 리듀서의 payload로 보내줍니다.

createReducer에는 getEmployees가 성공했을 때 실행되는 getEmployees.fulfilled 액션명에 대한 리듀서가 정의되어 있습니다.

리듀서 함수는 axios의 then안에서의 response.data를 payload로 받아 state.employees에 할당해줍니다.

리듀서 함수가 실행된 결과 store는 employees 데이터가 들어간 상태로 업데이트가 됩니다.

 

 

 

 

 

 

요청에 실패한 경우

 

400번대, 500번대 에러 상태 코드를 응답받거나 요청이 failed인 경우는 axios 반환 프로미스 객체에 catch로 받을 수 있습니다.

 

export const action = {
  getEmployees: createAsyncThunk("GET/EMPLOYEES", async (_, { rejectWithValue }) => {
    return axios({
      method: "get",
      url: "http://dummy.restapiexample.com/api/v1/employees"
    })
    .then(response => response.data)
    .catch(error => rejectWithValue(error.response.data));
  }),
};

 

이렇게 하면 컴포넌트 단에서 사용하는 dispatch().unwrap().catch()에서 rejectWithValue() 안에 있는 값을 받을 수 있습니다. 리듀서 로직을 타지 않고 컴포넌트 단에서 처리하고 싶은 로직은 이런 식으로 처리를 합니다.

 

* App.js

import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";

function App() {
  const employees = useSelector(state => state.employees.employees);
  const dispatch = useDispatch();
  const [errorMessage, setErrorMessage] = useState("");

  useEffect(() => {
    dispatch(action.getEmployees())
      .unwrap()
      .then(response => {
        console.log("### response: ", response);
      })
      .catch(error => {
        console.log("### error: ", error);
        setErrorMessage(error.message);
      });
  }, []);

  return (
    <div className="App">
      {errorMessage && <div>{errorMessage}</div>}
      {employees.map(employee => <div id={employee.id}>{employee.name}</div>)}
    </div>
  );
}

export default App;

 

마찬가지로 dispatch().unwrap().then() 에서는 axios().then() 에서 then() 안에서 반환하는 값을 파라미터를 통해 받아옵니다.

 

또한 실패한 비동기 요청은 `액션명/rejected` 액션을 디스패치합니다. 이에 대한 리듀서도 작성해줄 수 있습니다. 실패한 요청에 대해 리덕스 스토어 값 변경이 필요하다면 다음과 같이 사용해줍니다.

 

export const reducer = {
  // ...
  handleReject: (state, action) => {
    // state 업데이트 로직
  },
};

const employeesReducer = createReducer(initialState, builder => {
  builder
    .addCase(action.getEmployees.fulfilled, reducer.getEmployees)
    .addCase(action.getEmployees.rejected, reducer.handleReject) // 400, 500번대 이거나 요청이 failed 일때, rejected로 들어옴
});

 

 

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기