
React에 커스텀 훅으로 만들어서 처리하기
- 커스텀 훅은 개발자가 스스로 훅을 정의 할수 있고, 이를 이용해 반복되는 로직을 함수로 뽑아내어 재사용이 가능하다.
- 폼 데이터 관리, API 요청 상태 관리, 비동기 데이터 요청을 여러 곳에서 사용할 때
상태관리 로직의 재활용이 가능, 코드의 가독성 증가, 상태 관리
리액트 공식문서 예시
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
const { useState, useEffect } = React;
function useCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return count;
}
function CounterComponent() {
const count = useCounter();
return (
<div>
<h1>Current count: {count}</h1>
</div>
);
}
ReactDOM.render(<CounterComponent />, document.getElementById('root'));
- 커스텀 훅은 순수해야 한다.(부수효과X) 다른 데이터가 변경이 되거나 영향이 있으면 안된다.
- 커스텀 훅의 이름은 use뒤에 대문자로 시작되어야한다. 카멜케이스
- 모든 Hook은 컴포넌트가 재렌더링될 때 마다 재실행
- 함수는 조건부 함수가 아니어야 한다. return하는 값은 조건부여서는 안된다.(return count;)
내가 만든 커스텀 훅
import axios from 'axios'
import { useEffect, useState } from 'react'
const useMovieApi = (url) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
setError(null)
try {
const response = await axios.all(url.map((url) => axios.get(url)))
const combinedData = response.reduce(
(acc, res) => acc.concat(res.data),
[]
)
setData(combinedData)
} catch (e) {
setError(e.message || '에러입니다!')
}
}
fetchData()
}, [url])
return { data, error, loading }
}
export default useMovieApi
- url을 외부에서 받아서 그에 맞는 요청을 보내고 state에 상태를 저장한다.
- useEffect로 초기 렌더링 시 실행시키고, url이 바뀐다면 다시 함수를 실행하여 렌더링 해준다.
- useEffect훅은 동기적이기 떄문에 안에 로직을 긴 시간을 보내게 하는 코드를 작성하고 렌더링 해야한다면 사용자는 계속 로딩을 기달려야한다. 비동기로 처리하기
- 비동기로 처리하면은 사용자가 기다리는 동안, 다른 화면은 먼저 렌더링하고, 로딩이라는 것을 인지시켜 줄수 있다.
- 비동기로 처리하기 떄문에 콜스택을 떠나 웹 API로 가고 이벤트루프가 이를 관찰하면서 실행이 완료되면은 콜백큐에 넣어서 스택에 아무것도 없다면 밀어넣어서 업데이트 해주는 방식이다.
axios.all
const response = await axios.all(url.map((url) => axios.get(url)))
const combinedData = response.reduce(
(acc, res) => acc.concat(res.data),
[]
)
setData(combinedData)
- request를 여러번 보낼수 있다.
- 받아온 response는 soread요청으로 받아와야 한다.
- JavaScript 배열과 같은 프로미스(iterable)를 인자로 받고, 각 프로미스의 결과를 담은 배열을 반환
- axios.all 함수는 여러 개의 프로미스를 병렬로 실행하고, 모든 프로미스의 결과가 반환될 때까지 기다린 뒤 그 결과를 하나의 배열로 반환하는 함수
- acc는 누산기, 초기값은 지금([])이고, res는 배열의 각 요소, res.data로 data가 있다면 acc([])배열에 추가하고 작업이 다 끝나면 concat으로 새로운 배열로 합친다.
커스텀훅 데이터 받기
// URL 데이터 두 개 넘겨주기
const { data, loading, error } = useMovieApi ({
url: [MOVIE_URL, MOVIE2_URL],
})
// 구조 분해 할당으로 쓸꺼 가져오기
const [data1, data2] = data
오류
- 이 코드로 만들었을 떄 data를 못찾는 다는 오류가 계속 나서 정말 짜증났다.
- state에 처음 값이 [] 빈 배열이기 떄문에 비동기적 처리 때문에 받아서 쓰는 곳에서 data가 계속 없다고 뜬 것이다.
import axios from 'axios'
import { useEffect, useState } from 'react'
const useMovieApi = (url) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
setError(null)
try {
const response = await axios.all(url.map((url) => axios.get(url)))
const combinedData = response.reduce(
(acc, res) => acc.concat(res.data),
[]
)
setData(combinedData)
} catch (e) {
setError(e.message || '에러입니다!')
}
}
fetchData()
}, [url])
return { data, error, loading }
}
export default UseMovieApi
- 이를 해결하기 위해서 useEffect를 여기서 뺴고 받아 쓰는 곳에서 처리를 해주었고
- axios.all로 받아오는 데이터들에 대한 처리를 각각의 state로 관리하였다. 구조분해할당 사용
- 커스텀 훅을 사용하면서 안 사실을 렌더링 순서가 보장되어야한다. 즉 return에서 명시해주는 것에 받아쓰는 곳에서 순서를 잘 지켜야 한다는 것이다.
# 오류 2
const combinedData = response.reduce(
(acc, res) => acc.concat(res.data),
[]
)
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
- reduce와, concat으로 누적해서 새로운 배열로 만들수는 있지만 받아 쓰는 곳에서 구조분해 할당을 하면은 언디파인드 에러가 뜸
- concat 결과를 acc에 다시 할당하지 않으면, 원본 배열은 변경되지 않아서 데이터가 누적(X)
- 공식문서에서도
# 오류 3 커스텀훅 데이터 받기 1번 처럼 사용하면 에러가 나온다!
매번 하면서 느끼는 거지만 useMoemo, useCallBack을 활용해 최적화를 잘하는 방법은 어려운거 같다... 더 좋은 방법 있으면 알려주세요
const Main = () => {
const { data1, data2, loading, error, fetchMovies } = useMovieApi()
const mainImg = useMemo(() => {
return data1 && data1.length > 0
? `${TMDB_MAIN_IMG}${data1[0].backdrop_path}`
: ''
}, [data1])
useEffect(() => {
fetchMovies({ urls: [MOVIE_URL, MOVIE2_URL] })
}, [])
if (loading) return Loading...
if (error) return Error: {error}
if (data1 && data1.length > 0) {
return (
)
}
return No data available
}
export default Main
# 해결 방법
import axios from 'axios'
import { useState } from 'react'
const useMovieApi = () => {
const [data1, setData1] = useState([])
const [data2, setData2] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
async function fetchMovies({ urls }) {
setLoading(true)
setError(null)
try {
const [response1, response2] = await axios.all(
urls.map((url) => axios.get(url))
)
setData1(response1.data)
setData2(response2.data)
} catch (e) {
setError(e.message || 'Error')
console.error(e)
} finally {
setLoading(false)
}
}
return { data1, data2, error, loading, fetchMovies }
}
export default useMovieApi
지금까지 만든 화면

- 출처 axios
- 출처 리액트 공식문서
- API사용 TMDB API문서
틀린점이 있다면 댓글로 알려주세요! all을 더 좋게 처리하는 방법도 알려주시면 감사하겠습니다!
'리액트' 카테고리의 다른 글
React (0) | 2025.01.07 |
---|---|
React-player 라이브러리 (0) | 2024.12.31 |
React Potal Modal 구현하기 (3) | 2024.12.31 |
React scroll시 css 변경하기 (0) | 2024.12.31 |
React 캐로셀 (1) | 2024.12.31 |