- 일시 : 23.06.30 금요일
리액트는 16.8버전부터 새로 추가된 React Hooks라는 기능으로 기존에 Class 바탕의 코드 작성을 대체하게 하였다. 이는 class형 컴포넌트가 갖던 문제점 (길어지는 코드 길이, 중복코드, 가독성 문제 등)을 해결할 수 있었다.
이번 과제는 그 Hooks 중에서도 내가 필요한 기능을 작동하는 Custom Hook을 개발 및 적용하는 것이다. Custom Hook은 코드, 로직의 반복을 최소화하고 재사용성을 높이기 위해 사용한다. 이전에 만든 영화 웹사이트에서 영화API Fetch요청 부분을 Custom Hook으로 나타내어 보았다.
참고자료1 : 공식문서 - Hook의 개요
참고자료2 : 블로그 - Custom Hook이란?
처음엔 그냥 'fetch시 공통적으로 사용되는 코드를 쓰면 되지 않을까?'하여 다음과 같은 코드를 만들어 보았다.
// useFetchMovie.js
import { useEffect, useState } from "react";
const useFetchMovie = (url) => {
const [fetchedMovie, setFetchedMovie] = useState();
const fetchData = async (url) => {
const json = await (await fetch(url)).json();
setFetchedMovie(json);
};
useEffect(() => {
fetchData(url);
}, [url]);
return [fetchedMovie];
};
export default useFetchMovie;
// Home.js
import useFetchMovie from "../hooks/useFetchMovie";
const Home = () => {
const [source] = useFetchMovie(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=9.0&sort_by=year`
);
const movies = source.data.movies;
return (
<div>
<Header />
<MoviesWrapper>
{movies.map((movie) => (
<Movie
key={movie.id}
year={movie.year}
id={movie.id}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</MoviesWrapper>
</div>
);
};
export default Home;
하지만 이 코드를 적용 시켰을 때 첫 렌더링은 잘 나오지만 새로고침 이후로는 다음과 같은 에러가 발생했다.
Uncaught runtime errors라며 '해당 오류는 요소가 DOM에 존재하지 않거나 참조하는 요소가 없을 때 발생'하는 에러였는데 이를 통해 api를 제대로 받아오지 못한다는 것을 알 수 있었다.
하지만 Custom Hook을 만드는 방법은 무궁무진하다. 바로 다른 방법을 찾아보기 시작했다. 특히 수업에서 배운 useReducer + 강사님이 로딩, 데이터패치, 에러일 경우를 나타내라고 하셨기 때문에 여러 방법을 검색 중 이와 아주 적합한 코드를 발견하였다. 여기에 axios를 추가 설치하여 문법을 더 간소화 하였다. 이를 적용한 내 커스텀훅은 다음과 같다.
import { useReducer, useEffect } from "react";
// 세가지 타입에 따른 분류
function reducer(state, action) {
switch (action.type) {
case "LOADING":
return {
loading: true,
data: null,
error: null,
};
case "SUCCESS":
return {
loading: false,
data: action.data,
error: null,
};
case "ERROR":
return {
loading: false,
data: null,
error: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
// custom Hook
function useFetchMovie(callback, deps = []) {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: false,
});
const fetchData = async () => {
dispatch({ type: "LOADING" });
try {
const data = await callback();
dispatch({ type: "SUCCESS", data });
} catch (e) {
dispatch({ type: "ERROR", error: e });
}
};
useEffect(() => {
fetchData();
// eslint 설정을 다음 줄에서만 비활성화
// eslint-disable-next-line
}, deps);
return [state, fetchData];
}
export default useFetchMovie;
// Home.js
import axios from "axios";
import useFetchMovie from "../hooks/useFetchMovie";
async function getUsers() {
const response = await axios.get(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=9.0&sort_by=year`
);
return response.data;
}
const Home = () => {
const [state, refetch] = useFetchMovie(getUsers, []);
const { loading, data: users, error } = state;
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null;
const movies = users.data.movies;
return (
<div>
<Header />
<MoviesWrapper>
{movies.map((movie) => (
<Movie
key={movie.id}
year={movie.year}
id={movie.id}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</MoviesWrapper>
</div>
);
};
export default Home;
아직 useReducer 훅에 익숙하지 않은 나에게 직접 활용하고 이해할 수 있는 기회가 되었다.
출처 : useAsync 커스텀 Hook 만들어서 사용하기
(추가예정)
본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.