본문 바로가기

오픈소스/노드

[Node] React 정리(20) - useAsync 커스텀 훅 만들기, Context API를 사용하여 useAsync 커스텀 훅 적용

리액트로 프로젝트를 생성하여 개발하는 과정 중 리액트의 필수 문법 및 기초적인 부분, 심화 과정 등을 정리한 문서입니다. 정리한 부분에는 제가 이해하는 관점에서만 정리를 하였기 때문에 초점은 보여주는 형식이 아닌 제가 필요할 때 사용하려는 목적이 담겨져 있습니다. 

 

데이터를 요청해야 할 때마다 리듀서를 작성하는 것은 번거로운 일 입니다. 매번 반복되는 코드를 작성하는 대신에, 커스텀 Hook 을 만들어서 요청 상태 관리 로직을 쉽게 재사용하는 방법을 알아봅시다.

 

완성된 예제는 다음 링크를 통해 미리 보실 수 있습니다.

https://github.com/libtv/React_Note/tree/master/20.%20useAsync

 

GitHub - libtv/React_Note: This is a notebook organized by React.

This is a notebook organized by React. Contribute to libtv/React_Note development by creating an account on GitHub.

github.com

 

 

# Contents


  • useAsync 커스텀 훅 생성
  • ContextAPI 사용하여 useAsync 커스텀 훅 적용

 

 

# useAsync 커스텀 훅 생성


이번 예제에서는 자주 사용하고 있는 useEffect와 useReducer를 모아서 한 파일로 진행해보았습니다. 이렇게 사용하게 되면 변화하는 데이터에 따라 여러 개의 내부 페이지를 변경할 수 있다는 리렌더링할 수 있다는 장점이 있습니다.

src 디렉터리에 useAsync.js 파일을 생성하고, 다음 코드를 작성해보세요.

 

아래 코드를 통해 useAsync.js를 수정합니다.

 

import { useEffect, useReducer } from "react";

function reducer(state, action) {
    switch (action.type) {
        case "LOADING":
            return {
                ...state,
                loading: true,
            };

        case "RENDER":
            return {
                ...state,
                loading: false,
                data: action.data,
            };
    }
}

const initialState = {
    loading: true,
    data: null,
};

function useAsync(callback, deps) {
    const [variable, setVariable] = useReducer(reducer, initialState);

    const MyFunc = async () => {
        setVariable({
            type: "LOADING",
        });

        const response = await callback();

        setVariable({
            type: "RENDER",
            data: response.data,
        });
    };

    useEffect(() => {
        MyFunc();
    }, deps);

    return [variable, MyFunc];
}

export default useAsync;

 

아래 코드를 통해 App.js를 수정합니다.

 

import axios from "axios";
import React, { useCallback, useEffect, useReducer } from "react";
import styled, { css } from "styled-components";
import useAsync from "./useAsync";

const MyList = styled.h3`
    color: red;
    font-size: 15px;
`;

const MyButton = styled.button`
    font-size: 20px;
    background-color: aquamarine;
    ${(props) => {
        if (props.visible)
            return css`
                display: none;
            `;
    }}
`;

function reducer(state, action) {
    switch (action.type) {
        case "LOADING":
            return {
                ...state,
                loading: true,
            };

        case "RENDER":
            return {
                ...state,
                loading: false,
                data: action.data,
            };
    }
}

const myFunction = () => axios.get("https://jsonplaceholder.typicode.com/users");

function App() {
    const [state, dispatch] = useAsync(myFunction, []);
    const { loading, data } = state;

    const onClick = () => {
        return dispatch();
    };

    return (
        <div className="App">
            <>
                {loading || data == null
                    ? "로딩중입니다."
                    : data.map((list) => {
                          return (
                              <MyList>
                                  {list.id} : {list.name} - {list.email}
                              </MyList>
                          );
                      })}
                <MyButton onClick={onClick} visible={loading}>
                    다시 불러오기
                </MyButton>
            </>
        </div>
    );
}

export default App;

 

 

# Context API 사용하여 useAsync 커스텀 훅 적용


위에서 만들었던 useAsync 커스텀 훅을 Context API로 적용하여 보도록 하겠습니다. 여기서 사용될 훅은 4가지 입니다.우리가 만들었던 useAsync 커스텀 훅, Context 훅, useEffect 훅 그리고 useReducer 훅을 사용하였습니다. 아래 예제를 통해 접근하게 되면 리액트로 홈페이지를 자유 자재로 만들 수 있을 것 같다는 생각이 듭니다. 그러면 먼저 Context API를 정의해보도록 하겠습니다.

 

아래 코드는 UserContextProvider. js 를 생성하여 만든 코드입니다.

 

import React, { useContext, useReducer } from "react";

const UserStateContext = React.createContext();
const UserDispatchContext = React.createContext();

const initializeState = {
    loading: true,
    data: null,
};

function reducer(state, action) {
    switch (action.type) {
        case "LOADING":
            return { ...state, loading: true };

        case "RENDER":
            return { loading: false, data: action.data };
    }
}

function UserProvider({ children }) {
    const [state, dispatch] = useReducer(reducer, initializeState);

    return (
        <>
            <UserStateContext.Provider value={state}>
                <UserDispatchContext.Provider value={dispatch}>
                    <>{children}</>
                </UserDispatchContext.Provider>
            </UserStateContext.Provider>
        </>
    );
}

export default UserProvider;

export function useUserState() {
    return useContext(UserStateContext);
}

export function useUserDispatch() {
    return useContext(UserDispatchContext);
}

 

위에서는 상태값이랑 함수를 전송하기 위하여 각각의 Context를 생성하였습니다. 그리고 useReducer에 정의 될 함수와 초기상태를 정의해주었습니다. 간단하게 로딩상태와 데이터 값을 통해 로딩되고 렌더되는 함수를 위와 같이 코딩하였습니다. 그리고 UserProvider를 통해 각각의 Provider 들을 생성하여 자식들의 파라미터 값을 넣어주었습니다.

 

만든 코드들을 App.js 에서 임포트하여 적용시키보도록 하겠습니다.

 

아래 코드는 App.js 의 코드입니다.

 

import React from "react";
import Consumer from "./Consumer";
import UserProvider from "./UserContextProvider";

function App() {
    return (
        <div className="App">
            <>
                <UserProvider>
                    <Consumer></Consumer>
                </UserProvider>
            </>
        </div>
    );
}

export default App;

 

만든 코드들을 Consumer 에서 사용되고 있는 것을 확인할 수 있습니다. Consumer 에서는 useAsync 에서 생성한 함수와 변수를 사용하고 있습니다.

 

아래 코드는 Consumer.js 의 코드입니다.

 

import styled, { css } from "styled-components";
import useAsync from "./useAsync";
import axios from "axios";

const MyList = styled.h3`
    color: red;
    font-size: 15px;
`;

const MyButton = styled.button`
    font-size: 20px;
    background-color: aquamarine;
    ${(props) => {
        if (props.visible)
            return css`
                display: none;
            `;
    }}
`;

const myFunction = () => axios.get("https://jsonplaceholder.typicode.com/users");

function Consumer({ children }) {
    const [state, dispatch] = useAsync(myFunction, []);
    const { loading, data } = state;

    const onClick = () => {
        return dispatch();
    };

    return (
        <>
            {loading || data == null
                ? "로딩중입니다."
                : data.map((list) => {
                      return (
                          <MyList>
                              {list.id} : {list.name} - {list.email}
                          </MyList>
                      );
                  })}
            <MyButton onClick={onClick} visible={loading}>
                다시 불러오기
            </MyButton>
            {children}
        </>
    );
}

export default Consumer;

 

 

아래는 위에서 만든 useAsync를 수정한 내용입니다. 아래 코드에서는 useReducer를 제거하였고, 제거된 코드들은 Context에 적용되어 있습니다. 해당 파일에서는 Provider로 생성한 함수를 다듬고 새로운 함수를 리턴하고 있습니다.

 

아래 코드는 useAsync.js 의 코드입니다.

 

import { useEffect } from "react";
import { useUserDispatch, useUserState } from "./UserContextProvider";

function useAsync(callback) {
    const myDispatch = useUserDispatch();
    const myState = useUserState();

    const AsyncDispatch = async () => {
        myDispatch({
            type: "LOADING",
        });

        const response = await callback();

        myDispatch({
            type: "RENDER",
            data: response.data,
        });
    };

    useEffect(() => {
        AsyncDispatch();
    }, []);

    return [myState, AsyncDispatch];
}

export default useAsync;