React _미니프로젝트 기록2
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 값에 []를 주는 이유는 리액트의 랜더링은 데이터가 오는 시간을 알아서 기다려주지 않는다. 그래서 데이터를 받기 전에 로드가 되어서 계속 없는 값을 로드하게 되고 에러가 나오게 된다. 그래서 처음 값을 지정해서 줘야 처음에 빈 배열로 랜더링을 하고 후에 데이터가 들어오면 받은 데이터를 랜더링 해준다.
이 밖에도 다양한 에러와 상황이 있지만, 모든 에러를 기록하고 기억할 순 없을 거 같다.
다만 그 원인을 찾고 해결할 수 있는 능력이 있어야 진정한 개발자라고 할 수 있지 않을까?
여러 에러를 만나고 해결해 본 기억과 경험이 있어야 다음에 에러를 만나도 해결을 해 나갈 수 있을 거 같다.
쉬운 방법으로 쉽게 풀어서 코딩할 수 있지만 그러면 왠지 실력이 늘지 않을 거 같다.
왜 이 함수를 쓰는지, 기능을 쓰는지에 대한 근본적인 이유로 접근해봐야 할 거 같다.