Redux-toolkit 비동기 처리(with Next Js)

YooSeok2·2022년 7월 21일
1
post-thumbnail

소개

지난 시간에 Redux-toolkit(RTK)를 통해 카운터를 구현하면서 주요 API사용법에 대해 알아보았습니다.

이번에는 createAsyncThunk를 이용해 비동기 요청을 처리하는 방법과 Next에서 pre-render를 위해 제공하는 getStaticProps, getServerSideProps를 통해 어떻게 SSR(Server Side Rendering)하는지 알아보는 시간을 갖겠습니다.

리덕스 툴킷 createAsyncThunk를 설명하는 공식문서 링크

reply사이트에서 제공하는 더미데이터를 이용해 유저의 정보를 받아 화면에 노출하는 작업을 구현하면서 설명하겠습니다.

1. createAsyncThunk로 비동기 처리

createAsyncThunk는 문자열의 액션 타입과 프로미스를 반환하는 콜백 함수를 인자로 받는 함수입니다. 첫번째 인자로 받은 액션 타입을 접두사로 사용하여 프로미스 유형의 작업을 생성하고 두번째 인자로 받은 콜백 함수를 기반으로 프로미스 값을 반환하는 thunk 액션 생성자를 반환합니다.

createAsyncThunk Otions

  • type : 비동기 요청의 생명주기를 표현할 리덕스 액션 타입을 문자열로 받는다.

    예를 들어, "user/loadUser" 문자열을 타입으로 받으면 아래의 유형의 타입들이 생성된다.
    * pending: 'users/loadUser/pending'
    * fulfilled: 'users/loadUser/fulfilled
    * rejected: 'users/loadUser/rejected'

  • payloadCreator : 비동기 작업을 수행하고 프로미스 값을 반환하는 콜백함수를 받는다. 주로 서버와의 통신을 하는 용도로 쓰인다. 이 콜백함수는 두 가지의 인자를 받는다.
    - arg : 액션을 dispatch할 때 인자로 쓰일 id와 같은 단일 값
    - thunkAPI : 추가 옵션을 포함하는 객체

    초기 호출 시 생명주기 액션 타입중 '~/pending'이 전달되고, 성공적으로 수행이 되면 '~/fulfilled'이 전달된다. 만약 error가 발생 하면 '~/rejected'가 전달되면서 약속된 error를 반환하거나 thunkAPI.rejectWithValue을 통해 error를 반환한다.

코드 및 설명

import axios from 'axios';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { createSlice } from "@reduxjs/toolkit";
import { loadUser } from "../actions/user";

axios.defaults.baseURL = "https://reqres.in/";

export const loadUser = createAsyncThunk('users/loadUser', async (id, thunkApi) => {
   try {
     const response = await axios.get(`api/users/${id}`);
     return response.data.data;
   } catch(err) {
     return err;
     //or return thunkApi.rejectWithValue(err);
   }
})


interface User {
  id: number,
  email: string,
  first_name: string,
  last_name: string,
  avatar: string
}

interface UserState{
  loadUserLoading: boolean,
  loadUserDone: boolean,
  loadUserError: any,
  user: User | null
}

const initialState: UserState = {
   user: User | null
  loadUserLoading: false,
  loadUserDone: false,
  loadUserError: null
};

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {},
  
  //createAsyncThunk에서 전달 된 생명주기 액션들은 여기서 적절한 작업 수행
  extraReducers: (builder) => { 
    builder
      .addCase(loadUser.pending, (state) => {
        state.loadUserLoading = true;
      })
      .addCase(loadUser.fulfilled, (state, action) => {
        state.loadUserDone = true;
        state.user = action.payload;
      })
      .addCase(loadUser.rejected, (state, action) => {
        state.loadUserLoading = false;
        state.loadUserDone = true;
        state.loadUserError = action.payload;
      })
  }
});

유저 정보를 서버에서 받아와 값을 저장소에 저장하는 간단한 로직이다.

RTK는 createAsyncThunk에서 생성되는 액션createSlice의 extraReducers에서 처리하도록 설계되어져 있다.

addCase함수를 체이닝해서 전달 받은 생명주기 액션에 따라 적절한 작업을 수행한다.

2. 화면 구축 및 테스트

UserFrofile View 구축

import React, { useEffect } from 'react';
import { RootState } from '../store'
import { useSelector } from 'react-redux'
import { useAppDispatch } from '../store';
import { loadUser } from '../actions/count';

function UserProfile () {
  /* 
  	! useAppDispatch가 궁금하신 분들은 이전 포스트인 Redux-toolkit 사용법(feat. Next Js)를 
	  읽어보시길 바랍니다.
	! useSelector는 react-redux에서 제공하는 hooks로 저장소의 state값을 받아오는 일을 수행한다.
  */
  const dispatch = useAppDispatch(); 
  const {user} = useSelector((state:RootState) => state.count);
  
  useEffect(()=> {
    /* 
      위에서 createAsyncTunk로 생성해서 export한 유저 정보를 받아오는 비동기 액션 함수를 
      함수 컴포넌트가 렌더링 될 때 실행되게 useEffect에서 dispatch 하였다.
    */
    dispatch(loadUser());
  }, []);
  
  return(
    <>
      <div className={styles.loadUser}>
          <div>
            <p>: </p>
            <span>{user?.first_name}</span>
          </div>
          <div>
            <p>이름 : </p>
            <span>{user?.last_name}</span>
          </div>
          <div>
            <p>이메일 : </p>
            <span>{user?.email}</span>
          </div>
      </div>
    </>
  );
}

export default UserProfile;

테스트 결과 화면


테스트 결과 화면을 보면 처음 화면에는 유저 정보가 없다가 반짝하고 데이터가 받아와져 있는걸 보실 수 있습니다. 이는 최초 렌더링 되었을 때는 아직 서버에게 유저 정보를 응답 받지 못해 비워있는 상태였다가 응답 받은 후 재 렌더링 되면서 화면에 노출 되는겁니다.

"그렇다면 화면이 처음 렌더링 되기 전에 미리 유저 정보를 받을 수 없을까?"

Next Js에서 위의 문제를 해결하기 위한 API를 제공해 줍니다.
그 중 getStaticProps와 getServerSideProps를 통해 문제를 해결하고 알아보겠습니다.

2. getStaticProps 사용하기

페이지에서 getStaticProps(Static Site Generation) 함수를 호출하여 내보내면 NextJs에서는 getStaticProps에서 반한되는 값을 사용하여 해당 페이지를 pre-render한다.

NextJs getStaticProps 공식문서 링크

pre-render란?

NextJs에서는 기본적으로 모든 페이지를 미리 렌더링한다. 이는 더 나은 성능과 SEO를 제공한다.
최초에 생성되는 각 HTML은 필요로 하는 최소한의 자바스크립트 코드로 생성되어진다.

pre-render 방식에는 두 가지 형식이 있다.
1. Static-Generation : 처음 빌드 시 생성되는 HTML은 다음 요청 시 다시 사용된다.
2. Server-Side-Rendering : 매 요청마다 HTML을 다시 생성한다.

"getStaticProps는 언제 사용해야 할까?"

  • 페이지를 렌더링 하는데 앞서 미리 필요한 데이터가 있을 때 사용하면 좋다
  • getStaticProps는 최초 렌더링 할 때 HTML 및 JSON 파일을 성능을 위해 CDN에 캐시해놓기 때문에 빠르게 사전 렌더링을 수행 할 수 있고 이는 SEO 최적화에 도움이 된다.
  • getStaticProps는 /pages에 있는 페이지 파일에서 내보낼 수 있는데 _app, _error, _document와 같은 Next Js에서 별도의 용도로 사용되는 페이지에서는 안된다.

코드 및 설명

// pages/index

import type { NextPage } from 'next';
import {GetStaticProps} from 'next'; // getStaticProps type
import wrapper from '../store';
import { RootState } from '../store';
import { useSelector } from 'react-redux';
import { loadUser } from '../actions/count';

export const getStaticProps: GetStaticProps = wrapper.getStaticProps( 
  (store) => async ({preview}) => {
    // 필요한 작업을 작성하는 구간
   await store.dispatch(loadUser());
   return {
     // props: {}  -> 반환할 값 입력 구간
    revalidate: 60 // 60초 마다 getStaticProps를 재 실행한다.
  }
});

const Home: NextPage = () => {
  
  // react-redux에서 제공하는 hooks인 useSelector을 이용해 저장소에서 state값 user를 가져온다.
  const {user} = useSelector((state:RootState) => state.count);
  
  return (
    <div className={styles.container}>
      <main className={styles.main}>
         <div className={styles.loadUser}>
          <div>
            <p>: </p>
            <span>{user?.first_name}</span>
          </div>
          <div>
            <p>이름 : </p>
            <span>{user?.last_name}</span>
          </div>
          <div>
            <p>이메일 : </p>
            <span>{user?.email}</span>
          </div>
      </div>
      </main>
    </div>
  )
}

export default Home

3. getServerSideProps 사용하기

페이지에서 getServeSideProps(Server-Side_Rendering)을 호출하여 내보내면 Next에서는 이 함수에서 반환되는 값을 사용하여 매 요청마다 해당 페이지를 pre-render한다.

NextJs getServerSideProps 공식문서 링크

사용 TIPS

  • 매 요청마다 데이터를 가져와 페이지를 렌더링 해야 하는 경우에 사용하는것이 좋다.
  • getServerSideProps는 서버 측에서만 실행되며 브라우저에서는 실행되지 않는다.
  • /pages에 있는 페이지 파일에서만 내보낼 수 있다.

getSaticProps와 getServerSideProps의 사용 구분
getStaticProps는 최초 생성되고 이후로 더이상 데이터 업데이트가 필요없는 페이지에 사용하고, getServerSideProps는 빈번한 데이터 업데이트가 이루어지는 페이지에 사용한다.
getStaticProps 사용 페이지 예 - 회사소개(about) 페이지, 블로그 글 페이지 등
getServerSideProps 사용 페이지 예 - 좋아요 댓글 기능이 있는 게시글 페이지 등

코드 및 설명

// pages/index

import type { NextPage } from 'next';
import {GetServerSideProps} from 'next'; // getServerSideProps type
import wrapper from '../store';
import { RootState } from '../store';
import { useSelector } from 'react-redux';
import { loadUser } from '../actions/count';

export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps(
  store => async ({req, res, ...etc}) => {
  await store.dispatch(loadUser());
  return {
    // props: {} -> 반환할 값이 있을경우 작성
  };
});

const Home: NextPage = () => {
  
  // react-redux에서 제공하는 hooks인 useSelector을 이용해 저장소에서 state값 user를 가져온다.
  const {user} = useSelector((state:RootState) => state.count);
  
  return (
    <div className={styles.container}>
      <main className={styles.main}>
         <div className={styles.loadUser}>
          <div>
            <p>: </p>
            <span>{user?.first_name}</span>
          </div>
          <div>
            <p>이름 : </p>
            <span>{user?.last_name}</span>
          </div>
          <div>
            <p>이메일 : </p>
            <span>{user?.email}</span>
          </div>
      </div>
      </main>
    </div>
  )
}

export default Home
profile
아는만큼 보인다

0개의 댓글