안녕하세요. 김용성입니다.
오늘은 React의 custom hooks에 대해 알아보고 직접 만들어보는 시간을 가져보도록 하겠습니다.
요즘 React 개발 트랜드는 react hook을 적극 활용하는 것입니다. hook을 사용함으로 인해 class component 없이도 여러 state 값을 변경해줄 수 있게 되었고, 이는 React 개발자들이 function component를 통한 간결한 코드 작업을 할수있게끔 만들어주었습니다.
useState, useEffect 등등 hook을 사용하는 것만으로도 코드가 훨씬 보기 좋아졌지만, 반복되는 훅 활용 메소드들을 하나로 줄여줌으로써 더 간결하고 보기 좋은 코드를 만들 수 있는 것이 바로 custom hooks입니다. 이름에서 알 수 있다시피 custom hook은 개발자 본인이 직접 hook을 만드는 것이죠.
그렇지만 중요한 포인트가 있습니다. custom hook의 이름은 무조건!!!! use로 시작해야합니다. 이 점은 꼭 유념해주셔야 합니다.
Jsonplaceholder API를 사용하여 간단하게 구현해보도록 하겠습니다.
우선 UserList, UserDetail 컴포넌트를 생성했습니다. Jsonplaceholder에서 유저정보 10개를 받아 리스트로 보여주고 유저이름을 클릭하면 UserDetail page로 이동하는 아주 간단한 예제입니다.
//UserList.js
import React, { useEffect, useState } from "react"
import { Link } from "react-router-dom"
function UserList() {
const [userList, setUserList] = useState([])
const url = "https://jsonplaceholder.typicode.com/users"
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(resJson => setUserList(resJson))
}, [])
return userList.map(user => (
<Link
to={`/${user.id}`}
key={user.id}
className="list-group-item list-group-item-action"
>
<div>{user.name}</div>
</Link>
))
}
export default UserList
//userDetail
import React, { useEffect, useState } from "react"
import { useParams } from "react-router-dom"
function UserDetail(props) {
const [userInfo, setUserInfo] = useState([])
const { id } = useParams()
const url = `https://jsonplaceholder.typicode.com/users/${id}`
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(resJson => setUserInfo(resJson))
}, [])
return (
<div>
{userInfo.name}
<br />
{userInfo.email}
<br />
{userInfo.phone}
</div>
)
}
export default UserDetail
hook을 이용하여 쉽게 구현하였지만 아쉬움이 남습니다. 두 컴포넌트에서 data를 받아오는 useEffect 내의 fetch함수 부분이 똑같기 때문에 이를 하나로 통일시키고 싶습니다. 그럴때 우리는 custom hooks를 통해 이를 해결할 수 있습니다.
두 컴포넌트의 fetch함수를 useFetch라는 hook을 만들어서 통일시켜주도록 하겠습니다.
우선 hooks라는 디렉토리를 만든 후 useFetch.js를 만들어줍니다.
hooks 디렉토리를 꼭 만들어야하나요?
꼭 그래야 하는 것은 아니지만 나중에 custom hooks가 많아진다면 디렉토리 구조가 복잡해지겠죠?? 그럴때를 대비해서 hooks 디렉토리를 만들어 놓는 것이 보기 좋고 그때그때 꺼내서 사용하기도 편리합니다.
그럼 useFetch.js 내 코드를 작성해보도록 하겠습니다.
//useFetch.js
import { useEffect, useState } from "react"
function useFetch(url) {
const [data, setData] = useState([])
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(resJson => setData(resJson))
}, [url])
return data
}
export default useFetch
UserList,UserDetail 컴포넌트에서 겹쳤던 fetch부분을 그대로 넣어주고 data를 return 해주면 끝입니다. url을 인자로 받고 해당 url을 useEffect의 의존성 배열로 넣어주면 custom hook이 원할때 잘 호출되겠죠?? 이제 UserList, UserDetail 컴포넌트가 어떻게 변경할 수 있는지 살펴볼까요??
//UserList.js
import React from "react"
import { Link } from "react-router-dom"
import useFetch from "./hooks/useFetch"
function UserList() {
const userList = useFetch("https://jsonplaceholder.typicode.com/users")
return userList.map(user => (
<Link
to={`/${user.id}`}
key={user.id}
className="list-group-item list-group-item-action"
>
<div>{user.name}</div>
</Link>
))
}
export default UserList
//UserDetail.js
import React from "react"
import { useParams } from "react-router-dom"
import useFetch from "./hooks/useFetch"
function UserDetail(props) {
const { id } = useParams()
const userInfo = useFetch(`https://jsonplaceholder.typicode.com/users/${id}`)
return (
<div>
{userInfo.name}
<br />
{userInfo.email}
<br />
{userInfo.phone}
</div>
)
}
export default UserDetail
어떤가요?? 단 한줄로 useEffect와 useState 그리고 fetch 함수를 대체할 수가 있어졌습니다. 이전 코드보다 훨씬 간단해졌고 custom hooks의 네이밍이 잘되어있다는 가정하에 코드 가독성도 훨씬 높아지지 않았나요?? 지금은 fetch함수를 사용하는 component가 2개밖에 없지만 fetch함수를 사용하는 컴포넌트가 더 많으면 많을 수록 우리의 custom hooks는 그 힘을 발휘할 것입니다 ㅎㅅㅎ
이렇게 컴포넌트 로직을 뽑아내서 정의한 후 간편하게 호출할 수 있다는 것이 custom hooks의 정말 큰 장점이 아닐까 생각이됩니다.
이 밖에도 여러개의 input들이 존재하고 각각 여러개의 state 값을 control할 때에도 useInput이라는 custom hooks를 사용하여 코드를 간단하게 만들 수 있을겁니다. 아니면 user 정보를 useAuth라는 custom hooks를 만들어 그때그때 편하게 호출할 수도 있겠죠?
custom hooks를 좋은 네이밍과 더불어 사용하면 코드의 가독성을 높이고 짧고 간결한 코드를 작성할 수가 있을겁니다.
읽어주셔서 감사합니다.