Frontend Study

React _미니프로젝트 기록2

jimmmy_jin 2025. 1. 4. 13:32

API, 이 부분이 복잡하고 헷갈리는 부분이다. 상당히 많은 파라미터의 데이터 교환이 이루어지고

useState부터 useCallback, useEffect까지 거의 대부분의 기능이 나오는 거 같다.

그 말은 이 부분에 대한 자세한 이해가 있어야 한다는 말 같다.

 

일단 백엔드와의 api교환이 중요하고 웹프로그래밍의 끝은 api이기 때문에 집중해 보자.

사실 어려워진 이유도 코드를 중복해서 쓰지 않고 간결하고 유용하게 쓰기 위해서이다.

api를 주고받을 때마다 직접 코드를 하나하나 써서 주고받을 수 있지만 hook을 통해서

일괄 관리 할 수 있기 때문에 해당 방법을 잘 살펴봐야 한다.

1. SentHttpRequest

네이밍은 다르게 해도 상관없지만 대부분의 httpRequest의 형태는 비슷하다, Endpoint의 url로

정보를 주고받는다. 일단 아래와 같은 코드의 형태가 일반적으로 사용된다.

async function sendHttpRequest(url, config) {
  const response = await fetch(url, config);
  const resData = await response.json();

  if (!response.ok) {
    throw new Error(resData.message || "Something went wrong, please try again.");
  }
  return resData;
}

async를 사용했기 때문에 결과는 프로미스의 형태를 return 할 거다. 어쨌든 resData 안에는 해당 url을 통해 주고받은 데이터의 정보가 들어있다.

2. useHttp

다른 블로그 글에도 내용을 정리했듯이 아래의 세 가지가 대부분 필요하다.

  const [data, setData] = useState(initialData);
  const [error, setError] = useState();
  const [isLoading, setIsLoading] = useState(false);

 

데이터를 저장하고, 에러를 저장하고, loading을 처리하는 이 세 가지.

그리고 아래와 같은 방법으로 사용한다.

export default function useHttp(url, config, initialData) {
  const [data, setData] = useState(initialData);
  const [error, setError] = useState();
  const [isLoading, setIsLoading] = useState(false);

  const sendRequest = useCallback(
    async function sendRequest() {
      setIsLoading(true);
      try {
        const resData = await sendHttpRequest(url, config);
        setData(resData);
      } catch {
        setError(error.message || "Something went wrong, please try again.");
      }
      setIsLoading(false);
    },
    [url, config]
  );

  useEffect(() => {
    if ((config && (config.method === "GET" || !config.method)) || !config) {
      sendRequest();
    }
  }, [sendRequest, config]);

  return {
    data,
    error,
    isLoading,
    sendRequest,
  };
}

 

여기서 config 부분에 대한 이해가 좀 더 필요하다. 

useCallback을 사용하는 이유는 사용하지 않았을 때 또 무한적으로 함수가 실행되기 때문에

useCallback을 통해서 디펜던시를 걸어서 변화값이 있을 때에만 실행되게 하였고 아래의 useEffect를 통해서도

위의 값이 실행되었을 때 확인하는 과정을 해줬다. 

위 과정을 통해서 해당 hook을 사용하는 페이지는 간소해졌다. useState나 useEffect를 따로 사용하지 않게 됐고

위의 hook을 구조 분해 할당을 통해서 사용할 수 있다.

import useHttp from "../hooks/useHttp";
import MealItem from "./MealItem";

const requestConfig = {};

export default function Meals() {
  const {
    data: loadedMeals,
    isLoading,
    error,
  } = useHttp("http://localhost:3000/meals", requestConfig, []);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  return (
    <>
      <ul id="meals">
        {loadedMeals.map((meal) => (
          <MealItem key={meal.id} meal={meal} />
        ))}
      </ul>
    </>
  );
}

 

단 여기서 requestConfig를 만든 이유는, 파라미터에 직접적으로 {}를 전달했었는데 그 자체는 사실 진짜 빈 값이 아니다. 계속해서 값을 만들어내는 현상이 발생됐다. 그래서 고정적으로 함수 밖에  requestConfig={}라고 정의 후 사용하게 되면 에러가 사라진다.

 

그리고 세 번째 파라미타 즉 initialData 값에 []를 주는 이유는 리액트의 랜더링은 데이터가 오는 시간을 알아서 기다려주지 않는다. 그래서 데이터를 받기 전에 로드가 되어서 계속 없는 값을 로드하게 되고 에러가 나오게 된다. 그래서 처음 값을 지정해서 줘야 처음에 빈 배열로 랜더링을 하고 후에 데이터가 들어오면 받은 데이터를 랜더링 해준다. 

 

이 밖에도 다양한 에러와 상황이 있지만, 모든 에러를 기록하고 기억할 순 없을 거 같다. 

다만 그 원인을 찾고 해결할 수 있는 능력이 있어야 진정한 개발자라고 할 수 있지 않을까?

여러 에러를 만나고 해결해 본 기억과 경험이 있어야 다음에 에러를 만나도 해결을 해 나갈 수 있을 거 같다.

쉬운 방법으로 쉽게 풀어서 코딩할 수 있지만 그러면 왠지 실력이 늘지 않을 거 같다.

왜 이 함수를 쓰는지, 기능을 쓰는지에 대한 근본적인 이유로 접근해봐야 할 거 같다.