예전에 토이 프로젝트를 하면서 코드가 복잡해지거나 side effect가 많은(useEffect가 여러번 사용된다거나) 컴포넌트는 별 생각 없이 커스텀훅을 만들어서 분리를 했었다.
그때는 커스텀훅을 막연하게 코드의 가독성을 높이고 상태의 의존성을 줄이기 위해 쓰는 훅으로만 알았는데 문득 내가 쓰고 있는 방식이 옳은가에 대한 의문이 생겼다. 그래서 이번 기회에 확실하게 파헤쳐보려고한다.
코드의 중복을 줄이고 재사용성을 높이기 위해 개발자가 스스로 만든 훅을 말한다.
결론부터 말하자면 중복을 줄이고 재사용성을 높이는것이 가장 큰 장점이다.
예를 들어보자.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
서버에서 유저 정보를 받아와서 online/offline상태를 알려주는 컴포넌트이다.
이제 연락처 목록에서 온라인 상태인 사용자들의 이름을 초록색으로 표시하는 코드를 짠다고 가정해보자.
아마 위의 로직과 거의 흡사한 FriendListItem
이라는 컴포넌트를 만들면 될것이다.
import React, { useState, useEffect } from 'react';
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
리턴값 외에는 모든 로직이 동일하다. 이 경우에 동일한 코드를 커스텀 훅으로 만들고 로직이 필요한 컴포넌트에서 불러와 사용하면 되는것이다.
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
여기서 주의할 점은 커스텀훅은 use로 시작하는 자바스크립트 함수라는 점
이다. 여기서 한가지 의문이 들 수 있는데 리액트에 내장된 hook들은 jsx파일 즉, 컴포넌트 내에서만 사용할 수 있다는 것이다.
다시 말해 자바스크립트 함수에서는 hook을 사용할 수 없는데 유일하게 커스텀훅만이 다른 hook을 사용할 수 있다.
이제 위의 두 컴포넌트를 커스텀훅을 사용하게 바꿔주면 되는데
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
예전 코드와 정확히 동일한 방식으로 작동한다.
여기서 중요한 점은 같은 hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다.
커스텀훅을 사용할 때마다 그 안의 state와 effect는 완전히 독립적이다.
그렇다면 실제 코드에서 무엇을 기준으로 커스텀 훅을 만들어야할까?
내가 지금까지 했던 실수는 함수 컴포넌트의 길이가 길어지면 무조건 분리하려고 했던 점이다. 단지 코드가 길어졌고 가독성이 떨어졌다고 해서 분리할 필요는 없다. 커스텀 훅의 분리 기준은 세 가지 정도로 추려볼 수 있는데
이다.
여기서 1,2번에 대한 내용은 useReducer
에 관한 내용인데 다음에 자세하게 풀어보도록 하겠다.