Component, Hook 언제 분리할까
react
나만의(?) 분리 기준을 살펴보자
컴포넌트: UI 유사성은 분리 기준이 아니다
생김새가 비슷하다고 같은 컴포넌트로 묶으면 안 된다.
// ❌ "버튼이니까 Button 컴포넌트로"
function Button({
onClick,
type,
disabled,
loading,
icon,
iconPosition,
variant,
size,
fullWidth,
...
}) {
// 온갖 조건 분기
}
<Button onClick={handleSubmit} variant="primary" loading={isLoading} />
<Button onClick={handleCancel} variant="ghost" />
<Button onClick={handleDelete} variant="danger" icon={<Trash />} />
<Button onClick={handleShare} icon={<Share />} iconPosition="right" />
과도한 추상화
버튼마다 기능과 맥락이 다른데, props를 optional로 계속 뚫으면 조건 분기가 늘어나고, 타입이 복잡해지고, 수정할 때 사이드이펙트를 예측하기 어려워진다.
그럼 언제 분리하나?
기능이 응집될 때. 하나의 책임, 하나의 목적을 가질 때 분리한다.
// ✅ 기능이 같은 것끼리 묶음
function PostCard({ post }) {
return (
<article>
<h2>{post.title}</h2>
<p>{post.description}</p>
<TagList tags={post.tags} />
</article>
);
}
// PostList, RelatedPosts, SearchResults 등에서 동일한 목적으로 사용
PostCard는 "게시글을 카드 형태로 보여준다"는 하나의 목적만 가진다. UI가 비슷해서가 아니라, 하는 일이 같아서 분리한 것이다.
커스텀 훅: 훅이 여러 개라고 분리하는 게 아니다
// ❌ 독립적인 상태들을 그냥 묶어놓은 것 - 의미 없음
function usePostForm() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [tags, setTags] = useState("");
const [loading, setLoading] = useState(false);
return {
title, setTitle,
content, setContent,
tags, setTags,
loading, setLoading
};
}
이건 useState 4개를 감싼 것뿐이다. 각 상태가 서로 독립적으로 동작하고, 사용처에서 결국 개별적으로 다뤄진다. 분리했다고 복잡도가 줄어들지 않는다.
그럼 언제 분리하나?
상태와 side effect가 강하게 결합되어 유기적으로 동작할 때.
// ✅ 상태 + 구독 + 액션 + 부수효과가 하나의 흐름으로 동작
function useAuth({ requireAuth = false } = {}) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
setUser(user);
setLoading(false);
if (requireAuth && !user) router.push("/login");
});
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_, session) => setUser(session?.user ?? null)
);
return () => subscription.unsubscribe();
}, [requireAuth]);
const signOut = async () => {
await supabase.auth.signOut();
router.push("/");
};
return { user, loading, signOut };
}
이 훅은 "인증 상태 관리"라는 하나의 목적 아래, 상태 조회 → 구독 → 리다이렉트 → 로그아웃이 서로 엮여 동작한다.
정리
컴포넌트
- UI가 비슷하다고 분리하지 않는다
- 기능(책임)이 같을 때 분리한다
커스텀 훅
- 훅이 여러 개라고 분리하지 않는다
- 상태와 side effect가 하나의 목적으로 함께 동작할 때 분리
회고
참 어렵다. 같은 기준으로 프로젝트를 진행해본 적이 한번도 없는 것 같다. 분리하자니 너무 많은 폴더와 파일을 양산하는 것 같고, 분리 안하자니 일관성 있게 안짜는 것 같고...
line 수가 너무 길어진다는 이유로 분리할 때도 있고, 나중에 다른 곳에서 쓸 것 같다는 이유로 미리 추상화 할때도 있고...