기능적 구성 요소가 실제로 하나의 기능으로 작성되었지만 다른 기능과 마찬가지로 다른 기능으로 구성될 수도 있다. 모든 useState, useEffect, 기타 후크 또는 하위 구성 요소는 그 자체로 함수일 뿐이다. 즉 공통 패턴을 더 복잡한 동작을 캡슐화하는 새로운 기능으로 추출함으로써 해결할 수 있다.
복잡한 구성요소를 리팩토링하는 확실한 방법은 이를 하위 구성요소로 분해하는 것이다. 때로는 구성 요소의 후크 논리를 살펴보는 것만으로 새로운 추상화를 찾을 수도 있다.
function Dashboard() {
const [repos, setRepos] = useState<Repo[]>([]);
const [isLoadingRepos, setIsLoadingRepos] = useState(true);
const [repoError, setRepoError] = useState<string | null>(null);
useEffect(() => {
fetchRepos()
.then((p) => setRepos(p))
.catch((err) => setRepoError(err))
.finally(() => setIsLoadingRepos(false));
}, []);
return (
<div className="flex gap-2 mb-8">
{isLoadingRepos && <Spinner />}
{repoError && <span>{repoError}</span>}
{repos.map((r) => (
<RepoCard key={i.name} item={r} />
))}
</div>
);
}
use Hook로직을 CustomHooks로 추출하려면 이름이 (이 경우 useRepos) 로 시작하는 함수에 코드를 복사하면 된다.
/**
* Hook for fetching the list of all repos for the current user.
*/
export function useRepos() {
const [repos, setRepos] = useState<Repo[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchRepos()
.then((p) => setRepos(p))
.catch((err) => setError(err))
.finally(() => setIsLoading(false));
}, []);
return [repos, isLoading, error] as const;
}
function Dashboard() {
const [repos, isLoadingRepos, repoError] = useRepos();
return (
<div className="flex gap-2 mb-8">
{isLoadingRepos && <Spinner />}
{repoError && <span>{repoError}</span>}
{repos.map((i) => (
<RepoCard key={i.name} item={i} />
))}
</div>
);
}