프론트엔드 개발자의 기록 공간

[React] React(8) 본문

프로그래머스 데브코스_FE/TIL

[React] React(8)

[리우] 2022. 1. 28. 02:12

❗❗  데브코스 54일차 (10.14)

React7에 이어서 Custom Hook 만들어보기 과정으로 계속 진행된다.

 

✅ useAsync- Custom Hook

비동기 로직을 제거하기 위해 사용되는 hook

네트워크 로직이나 타임아웃과 같은 로직에 있을 때 사용할 수 있다.

useAsync 또한 두 가지 버전으로 만들 수 있다.

  1.  함수 호출로 실행되는 hook
  2.  컴포넌트가 로드되면 실행되는 hook

✍ 1. 함수 호출을 통한 방법 (useAsyncFn)

import { useCallback, useRef, useState } from "react";
		// props로 함수, 의존성 받기
const useAsyncFn = (fn, deps) => {
  // 실행에 대한 Id
  const lastCallId = useRef(0);
  // 로딩 여부 상태
  const [state, setState] = useState({ isLoading: false });
  // 실행할 함수
  const callback = useCallback((...args) => {
    const callId = ++lastCallId.current;
    // 함수가 로딩 상태가 아니라면 로딩 중으로 변경
    if (!state.isLoading) {
      setState({ ...state, isLoading: true });
    }
    // 함수 호출후 비동기 호출
    return fn(...args).then(
      // 비동기 로직 성공 시
      (value) => {
        // 비동기 함수 호출이 마지막일 경우만
        callId === lastCallId.current && setState({ value, isLoading: false });
        return value;
      },
      // 비동기 로직 실패 시
      (error) => {
        callId === lastCallId.current && setState({ error, isLoading: false });
        return error;
      }
    );
  }, deps);

  return [state, callback];
};

export default useAsyncFn;

 

위 코드에서 lastCallId의 역할은 비동기 로직이 여러 번 호출될 경우 마지막 값만 상태에 넘기기 위한 안전장치이다.
ex) 첫 번째 비동기가 들어오고 두 번째 비동기가 들어올 때
두 번째 비동기만 처리해 줘야 하는데 첫 번째 비동기가 더 늦게 처리가 되면
첫 번째 비동기 처리에 대한 결과값이 저장된다.
이를 방지해 줘야 한다.

 

📚 useAsyncFn - StoryBook 코드

import useAsyncFn from "../../hooks/useAsyncFn";

export default {
  title: "Hook/useAsyncFn",
};

// 비동기 호출 성공 로직
const asyncReturnValue = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Success");
    }, 1000);
  });
};

// 비동기 호출 실패 로직
const asyncReturnError = () => {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

// 비동기 호출 성공 로직
export const Success = () => {
  const [state, callback] = useAsyncFn(async () => {
    return await asyncReturnValue();
  }, []);

  return (
    <div>
      <div>useAsyncFn 테스트</div>
      <div>{JSON.stringify(state)}</div>
      <button onClick={callback} disabled={state.isLoading}>
        비동기 호출
      </button>
    </div>
  );
};

// 비동기 호출 실패 로직
export const Error = () => {
  const [state, callback] = useAsyncFn(async () => {
    return await asyncReturnError();
  }, []);

  return (
    <div>
      <div>useAsyncFn 테스트</div>
      <div>{JSON.stringify(state)}</div>
      <button onClick={callback} disabled={state.isLoading}>
        비동기 호출
      </button>
    </div>
  );
};

onClick 버튼을 클릭하게 되면 callback 함수가 실행이 되면서 asyncReturnValue() or  asyncReturnError()를 실행한다. 성공 했을 때, 실패 했을 때에 따라 "Success" or "Error" 이 반환되면서

DOM 화면에 결과값이 보이게 된다. ( <div>{JSON.stringify(state)}</div> )

 

✍ 2. 컴포넌트가 로드되면 실행 (useAsync)

이 경우에는 위에 작성한 useAsyncFn useHook을 이용하면 간단하다.

import { useEffect } from "react";
import useAsyncFn from "./useAsyncFn";

const useAsync = (fn, deps) => {
  const [state, callback] = useAsyncFn(fn, deps);
  // 컴포넌트 로드시 바로 실행.
  // callback 함수가 변경될 때 재 실행
  useEffect(() => {
    callback();
  }, [callback]);

  return state;
};

export default useAsync;

 

📚 useAsync - StoryBook 코드

import useAsync from "../../hooks/useAsync";

export default {
  title: "Hook/useAsync",
};

const asyncReturnValue = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Success");
    }, 1000);
  });
};

const asyncReturnError = () => {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

export const Success = () => {
  const state = useAsync(async () => {
    return await asyncReturnValue();
  }, []);

  return (
    <div>
      <div>useAsyncFn 테스트</div>
      <div>{JSON.stringify(state)}</div>
    </div>
  );
};

export const Error = () => {
  const state = useAsync(async () => {
    return await asyncReturnError();
  }, []);

  return (
    <div>
      <div>useAsyncFn 테스트</div>
      <div>{JSON.stringify(state)}</div>
    </div>
  );
};

여러 개의 네트워크 요청이 수행될 때, 마지막에 수행된 결과값을 따로 저장하고 싶을 때와 같은 상황에서
사용할 수 있다.

 

useAsyn 실제 사용 예시

import axios from 'axios'
import useAsync from './hooks/useAsync'

const App = () => {
  const initialPost = useAsync(async () => {
    return await axios
      .get('https://jsonplaceholder.typicode.com/posts')
      .then((responce) => responce.data)
  }, [])

  console.log(initialPost)
}

export default App

https://jsonplaceholder.typicode.com/ 무료 API 테스트 사이트를 이용해서 실습을 진행했다.

axios 비동기 통신 라이브러리를 이용하여 위와 같이 비동기 처리를 진행 할 수 있다.

 

👨‍💻 비동기 처리 hook을 학습하면서 비동기에 대해 고민해 볼 수 있었다. 당연하게 여러 개의 비동기가 발생되면
처리되는 시점은 제각각인데 이를 어떻게 처리해 볼까라는 생각을 했었다. 처음에는 async await를 적절히 사용하면 되지 않을까 생각했는데 그러면 비동기 처리를 순차적으로 진행되다 보니 마지막 비동기 처리 순간의 경우 많이 딜레이 되는 상황이 생길 수 있을 것 같았다. 하지만 강의를 보고 간단하게 useRef를 활용한 지역변수를 이용해서 판단하는 조건으로 해결할 수 있는 방법을 깨달았다.

 

📖 학습한 내용

  • React(8) - 사용자 정의 Hook 연습하기
    • useDebounce
    • useAsync
    • useHotKey
    • Modal - 컴포넌트 연습하기
    • Toast - 컴포넌트 연습하기
728x90

'프로그래머스 데브코스_FE > TIL' 카테고리의 다른 글

중간 프로젝트!!  (0) 2022.02.07
[React] React(9)  (0) 2022.01.28
[React] React(7)  (0) 2022.01.26
[React] React(6)  (0) 2022.01.26
[React] React(5)  (0) 2022.01.24
Comments