Programming/Client

Javascript / React에서 Fetch 올바르게 사용하기 (with Node.js 테스트 환경)

Jiwoo 2023. 5. 21. 18:02

들어가며

최근 Fetch를 사용하던 중 단순히 javascript에서 사용할 때와
react에서 사용할 때 차이점이 있어 이를 공부하게 됐다.
fetch는 비동기로 동작하기 때문에 신경쓸 점이 많았다.
그리고 테스트 환경에서 사용하려면 추가적으로 필요한 설정이 있기에
이것까지 정리해보려 한다!

🍀 Fetch란?

ES6에 도입된 자바스크립트 내장 함수로, 클라이언트가 서버에 네트워크 요청을 보내서 정보를 받아올 때 사용함

  • Promise 기반이라 비동기로 응답 받은 데이터를 다루기 쉬움
  • 내장 함수라서 모듈 설치할 필요가 없음

기본 문법

fetch의 기본 메소드(생략시)는 get

fetch(url, {
  method: "GET", // 기본값:GET (POST, PUT, DELETE, etc.)
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({...}),
});

💡 await
let response = await promise

  • promise.then에 연결한 것처럼 비동기처리에 순서를 부여하고, 반환값을 받음
  • async 함수 안에서만 동작함

Promise

일반 함수처럼 값을 받아오면 비동기적인 동작을 기다려주지 않기 때문에
비동기적인 함수의 값을 받는 방식이 따로 필요함
그 방식 중 하나가 Promise

💡 만약 프라미스 사용하지 않는다면?

데이터를 받고 콜백 함수를 중첩해서 불러오는 과정에서 에러 핸들링이 어렵다.
가독성이 나쁘고 콜백지옥이 생성되며 오류를 일으킬 확률↑


프라미스의 사용 효과

데이터를 받음 → (성공) 실행 → (실패) 에러 대응

프라미스는 성공/실패 각각의 경우에 js 내부 함수를 활용해서 실행되는 구조를 만들 수 있음

🍀 fetch로 통신하기

보통 fetch로 데이터를 받아오는 함수를 따로 만들고, 이를 컴포넌트에 불러온다.

그런데 프로미스를 받아와서 컴포넌트에서 처리할지

아니면 변환이 완료된 값 자체를 받아올지 형태는 다양하다.

javascript

데이터를 불러오는 함수를 분리해서 사용할 경우,
사용하는 함수를 async로 감싸 await로 값을 받으면 되므로 간단하다.

// utils/fetchData
export default async function fetchData() {

  const url = 'http://localhost:3000/data';
  const response = await fetch(url);
  const { data } = await response.json();
  return data; // 프로미스가 반환
}

// src/components/Table
async function Table() {
    const data = await fetchData(); // 프로미스의 값을 받음
      // 상태가 없기 때문에 데이터가 들어올 때 리렌더링되는 처리는 따로 필요함
}

react

데이터 전달 함수에서 프로미스를 반환 받느냐
아니면 변환 완료된 데이터를 받느냐로 나눠서 정리했다.


1. 데이터 전달 함수에서 프로미스를 반환받음

💡 주의사항
useEffect의 콜백함수가 async 함수가 될 순 없음
그러므로 콜백함수 안에 async 함수를 만들고, 실행해야함

// hooks/useFetchData
export default async function useFetchData() {
  const url = 'http://localhost:3000/data';
  const response = await fetch(url);
  const { data } = await response.json();
  return data; // 프로미스가 반환됨
}

// components/Table
export default function Table() {
  const [data, setData] = useState([])

  useEffect(() => {
    (async () => {
      const fetchedData = await useFetchRestaurants();
      setRestaurants(fetchedData)
    })(); // 만들고 바로 실행
  }, []);

  return (
    <div>
      {data && (
        <p>{data}</p>
      )}
    </div>
  );
}

2. 데이터 전달 함수에서 변환 완료하여 전달받음

// hooks/useFetchData
export default function useFetchData() {
  const [data, setData] = useState([]) // 여기서 상태값 설정

  const fetchData = async () => {
    const url = 'http://localhost:3000/data';
    const response = await fetch(url);
    const { data } = await response.json();

    setData(data);
  };

  useEffectOnce(() => {
    fetchData();
  });

  return data;
}

// src/components/Table
// 위에서 restaurants을 바로 넘겨주면 Table을 async 함수로 만들고 await으로 받아야 하는데
// 리액트의 함수 컴포넌트는 async 함수가 될수 없음
function Table() {
    const restaurants = useFetchData();
}

💡 비동기로 전달받은 데이터 저장을 변수가 아닌 state에 하는 이유


비동기 작업은 동기적인 흐름과 별개로 작업이 처리됨
그래서 일반 변수에 할당할 경우, 비동기 작업이 완료(데이터가 받아와짐)되기 전에
해당 변수가 사용되는 동작이 실행되기 때문에 변수 안에 값은 할당되더라도 의도한대로 화면이 그려지지 않음
하지만 상태값을 사용하면 비동기 작업이 완료되어 상태값이 업데이트되면
컴포넌트가 리렌더링되기 때문에 무조건 데이터가 완료된 상태가 최종 출력값이 됨

🍀 에러처리

에러처리가 간단한 설명을 위해 위 코드에서 생략됐지만 비동기 통신시에 꼭 필요하다.

fetch는 404에러같은 HTTP 에러 응답을 받아도 promise를 reject하지 않고

네트워크 장애가 발생한 경우만 reject한다

이후 console.log(error)로 error객체 확인 후, 처리하면 된다

  • error.ok : URL 엔드포인트 성공적으로 요청됐는지 여부
  • error.status : 상태 코드 (성공하면 200)

async & await 사용 예시

리액트 2번 방법에 적용

// 
// hooks/useFetchData
export default function useFetchData() {
  const [data, setData] = useState([]) // 여기서 상태값 설정

  const fetchData = async () => {
    const url = 'http://localhost:3000/data';
    const response = await fetch(url);
    const { data } = await response.json();

    if(!response.ok) {
        console.log(err.message);
    }

     setData(data);
  };

  useEffectOnce(() => {
    fetchData();
  });

  return data;
}

then & catch 사용 예시

fetch(url)
  .then((response) => {
    if (!response.ok) { // HTTP에러 발생 -> false
      throw new Error( // catch로 에러 전달
        `This is an HTTP error: The status is ${response.status}` // response.status = 404
      );
    }
    return response.json();
  })
  .catch((err) => {
    console.log(err.message);
  });

🍀 테스팅 환경(Node.js)에서 fetch 사용하기

fetch는 자바스크립트 메서드이기 때문에 Node.js에서는 실행되지 않는다.

그렇기에 이를 도와주는 프레임워크가 필요하다.

GitHub에서 만든 fetch polyfill인 whatwg-fetch 설치해서 사용하면 된다

(저장소 링크)


  1. 테스트 파일에 whatwg-fetch 불러오기

    import 'whatwg-fetch';

    테스트에서 사용시 모든 파일 상단에 쓰거나, setupTests 파일 상단에 넣으면 됨

  1. window.fetch()로 사용하기

    fetch 작성시에 앞에 window를 붙여주면 끝!

    window.fetch(url);