#1. 비동기 처리

1. Redux에서 비동기 처리하기

  • Redux는 기본적으로 동기 처리만 지원하기 때문에 비동기 처리를 하기 위해서는 확장 패키지가 필요하다.
  • ex) Redux-Thunk, Redux-saga 등의 미들웨어

 

1) slice 설정

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

// TODO: 비동기 처리 함수 구현
// payload는 이 함수를 호출할 때 전달되는 파라미터
export const 액션함수 = createAsyncThunk('액션함수별칭', async (payload, {rejectWithValue}) => {
  let result = null;

  try {
    result = await axios.get(url 및 파미터);
  } catch(error) {
    // rejectWithValue는 에러 발생시 사용
    result = rejectWithValue(error.response);
  }

  return result;
});

// TODO: Slice 정의 (Action함수 + Reducer의 개념)
const slice이름 = createSlice({
  name: 'department',
  initialState: {
    // 상태값 구조 정의 (자유롭게 구성 가능)
    data: null,         // Ajax 처리를 통해 수신된 데이터
    loading: false,     // 로딩 여부
    error: null,        // 에러 정보
  },

  // 내부 action 및 동기 action시 (ajax 처리시에는 사용하지 않음)
  reducers: {},

  // 외부 action 및 비동기 action (ajax 처리시 사용)
  // 액션함수에서 파생된 3개의 함수 -> 1세트
  extraReducers: {
  
    // Ajax 요청 준비
    [액션함수.pending]: (state, {payload}) => {  // pending -> 로딩
      // state 값을 적절히 수정하여 리턴한다.
      return {...state, loading: true}
    },
	
    // Ajax 요청 성공
    [액션함수.fulfilled]: (state, {payload}) => {  // fulfilled -> ajax완료
      return {
        data: payload?.data,
        loading: false,
        error: null,
      }
    },
    
    // Ajax 요청 실패
    [액션함수.rejected]: (state, {payload}) => {  // rejected -> error
      return {
        data: payload?.data,
        loading: false,
        error: {
          code: payload?.staus ? payload.staus : 500,
          message: payload?.stausText ? payload.stausText : 'Server Error'
        }
      }
    }
  }
});

// 리듀서 객체 내보내기
export default slice이름.reducer;
  • createAsyncThunk 함수 호출시 사용된 payload는 이 함수를 호출할 때 전달되는 파라미터이다.
  • rejectWithValue 함수는 에러 발생시 에러 데이터를 전달하면 extraReducerrejected 함수가 호출된다.
  • extraReducer 에는 사용할 액션함수에서 파생된 3개의 함수가 있는데 이 함수들은 1세트로 봐야한다.
    • [액션함수.pending]: Ajax 요청 준비 (로딩)
    • [액션함수.fulfilled]: Ajax 요청 성공 (Ajax 완료)
    • [액션함수.rejected] : Ajax 요청 실패 (에러)

 

2) store 설정

  • 보통은 slice를 먼저 작성 후 store를 작성한다. (기본틀은 먼저 작성해도 무관!)
import { configureStore } from '@reduxjs/toolkit';
import slice이름 from '...';

const store = configureStore({
  reducer: {
    // 개발자가 직접 작성한 reducer들이 명시되어야 한다.
    slice별칭: slice이름,
  },

  // 미들웨어를 사용하지 않을 경우 이 라인 생략 가능 (redux-thunk 사용시 필수)
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({serializableCheck: false}),

  // redux-devtools-extention을 사용하지 않을 경우 false 혹은 이 라인을 명시 안함
  devTools: true,
});

export default store;
  • 미들웨어 사용 설정에서 동기 처리 체크(serializableCheck)를 사용할지 안할지 설정한다.
    • true는 사용,  false는 사용안함 (또는 생략)
  • redux-devtools-extention을 사용하지 않을 경우 false 또는 명시하지 않는다.

 

 redux-devtools-extention 사용하기

  • 리덕스의 상태를 크롬브라우저 개발도구에 설치된 확장 기능과 연동할 수 있게 해주는 미들웨어이다.
  • redux-logger와 기능이 비슷하기 때문에 취향에 따라 사용하면 된다.
    • 리덕스가 관리하는 상태값을 자동으로 로그에 표시해준다.

- 패키지 설치

yarn add redux-devtools-extension

- 크롬 웹스토어 설치

  • 패키지 설치와 크롬 웹스토어 설치가 끝나면 개발자도구에서 사용할 수 있다.

  • devTools: false 로 설정하면 아래 이미지와 같이 찾을 수 없다는 메세지가 나온다.

 

3) 필요한 이벤트 핸들러 안에서 액션함수 디스패치

  • slice에서 정의한 액션함수의 action.payload 파라미터로 전달된다.
  • 다수의 파라미터가 필요한 경우 JSON 객체로 묶어서 전달한다.
dispatch(액션함수1(파라미터));
dispatch(액션함수2(파라미터));

 

2. 실습

※ 데이터 연동은 Json-server 사용

- index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// 리덕스 구성을 위한 참조
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

 

- App.jsx

import React from 'react';
import Department from './pages/Department';

function App() {
  return (
    <div>
      <h1>비동기 처리</h1>
      <Department />
    </div>
  );
}

export default App;

 

- DepartmentSlice.jsx

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

// TODO: 비동기 처리 함수 구현
export const getList = createAsyncThunk('department/getList', async (payload, {rejectWithValue}) => {
  let result = null;

  try {
    result = await axios.get('http://localhost:3001/department');
  } catch(error) {
    // rejectWithValue -> 에러 발생시 사용
    result = rejectWithValue(error.response);
  }

  return result;
});

// TODO: Slice 정의 (Action함수 + Reducer의 개념)
const departmentSlice = createSlice({
  name: 'department',
  initialState: {
    data: null,         // Ajax 처리를 통해 수신된 데이터
    loading: false,     // 로딩 여부
    error: null,        // 에러 정보
  },

  // 내부 action 및 동기 action을 할때는? (ajax와 상관이 없을 때)
  reducers: {},

  // 외부 action 및 비동기 action을 할때는? (ajax용)
  // getList에서 파생된 3개의 함수 -> 1세트
  extraReducers: {
    [getList.pending]: (state, {payload}) => {  // pending -> 로딩
      return {...state, loading: true}
    },

    [getList.fulfilled]: (state, {payload}) => {  // fulfilled -> ajax완료
      return {
        data: payload?.data,
        loading: false,
        error: null,
      }
    },

    [getList.rejected]: (state, {payload}) => {  // rejected -> error
      return {
        data: payload?.data,
        loading: false,
        error: {
          code: payload?.staus ? payload.staus : 500,
          message: payload?.stausText ? payload.stausText : 'Server Error'
        }
      }
    }
  }
});

export default departmentSlice.reducer;

 

- store.jsx

import { configureStore } from '@reduxjs/toolkit';
import departmentSlice from './slices/DepartmentSlice';

const store = configureStore({
  reducer: {
    // 개발자가 직접 작성한 reducer들이 명시되어야 한다.
    department: departmentSlice,
  },

  // 미들웨어를 사용하지 않을 경우 이 라인 생략 가능 (redux-thunk 사용시 필수)
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({serializableCheck: false}),

  // redux-devtools-extention을 사용하지 않을 경우 false 혹은 이 라인을 명시 안함
  devTools: true,
});

export default store;

 

- Department.jsx

import React, { memo, useEffect } from 'react';

// 상태값을 로드하기 위한 hook과 action 함수를 dispatch할 hook 참조
import { useSelector, useDispatch } from 'react-redux';
// Slice에 정의된 액션함수들 참조
import { getList } from '../slices/DepartmentSlice';

const Department = () => {

  // hook을 통해 slice가 관리하는 상태값 가져오기
  const {data, loading, error} = useSelector((state) => state.department);

  // dispatch 함수 생성
  const dispatch = useDispatch();

  // 컴포넌트가 마운트 되면 데이터 조회를 위한 액션함수를 디스패치함
  useEffect(() => {
    dispatch(getList())
  }, [dispatch]);

  return (
    <div>
      {error ? (
        <h1>{error.code} Error.</h1>
      ) : (
        <table border='1'>
          <thead>
            <tr>
              <th>id</th>
              <th>dname</th>
              <th>loc</th>
            </tr>
          </thead>
          <tbody>
            {data && data.map(({id, dname, loc},i) => (
              <tr key={id}>
                <td>{id}</td>
                <td>{dname}</td>
                <td>{loc}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
};

export default memo(Department);

※ 출력결과

'국비수업 > React' 카테고리의 다른 글

[React/Redux] Redux  (0) 2022.05.20
[React] Axios-hooks  (0) 2022.05.17
[React] Axios 사용하기  (0) 2022.05.15
[React] Hooks  (0) 2022.04.29
[React] CSS 사용하기  (0) 2022.04.26

+ Recent posts