- 컴포넌트 여행기(0) – Intro
- 컴포넌트 여행기(1) – 컴포넌트 분리하기 (현재글)
- 컴포넌트 여행기(2) – jsx 가독성 높이기
- 컴포넌트 여행기(3) – 스타일링
- 컴포넌트 여행기(4) – 상태관리
결론
- 관련 있는 컴포넌트들을 모아놓자.
- 공통 컴포넌트는 UI라이브러리를 만든다는 생각으로 작성하자(도메인 제거).
- 도메인이 겹치고 + 페이지간에 재사용되는 컴포넌트는 외부로 분리한다.
- 복잡한 추상화보다는 반복이 낫다.
- 적당히 나누자. 완벽히 나누게 되면 마음은 편하지만 코드를 찾아가는 경로가 길어지고 context를 잃게 된다.
관련 있는 컴포넌트들을 모아놓자
page 파일 안에 하위 컴포넌트 배치하기
// src/pages/detail-page/DetailPage.tsx
import Flex from '@shared/flex';
const DetailPage: React.FC = () => {
const { isFetching, isError, isSuccess } = useQuery( ... );
return (
<Self>
<Flex>
<Sidebar>
{isFetching && <Loading />}
{isError && <Error />}
{isSuccess && <MemberList />}
</Sidebar>
<Main>
<Flex flexDirection="column" rowGap="40px">
<StudyList />
<TodoList />
</Flex>
</Main>
</Flex>
</Self>
);
};
export default DetailPage;
const Self = () => { ... };
const Main = () => { ... };
const Sidebar = () => { ... };
const Loading = () => { ... };
const Error = () => { ... };
const MemberList = () => { ... };
const StudyList = () => { ... };
const TodoList = () => { ... };
이렇게 아래쪽에 배치하면 세가지 장점이 있습니다.
부모 컴포넌트로 빠르게 돌아올 수 있습니다
컴포넌트를 세세하게 파일별로 분리하면 VSC에서 부모컴포넌트 -> 하위 컴포넌트로 가는건 한번에 갈 수 있지만(ctrl + 우클릭), 다시 부모 컴포넌트로 오는건 개발자가 폴더 구조를 보고 찾아오거나 검색을 해야 하기 때문에 번거롭습니다.
아래쪽에 두게되면 해당 컴포넌트를 찾을때는 ctrl + 우클릭으로 한번에 가고, 돌아올때는 스크롤로 오면 되기 때문에 더 편합니다.
소속을 확실히 할 수 있습니다
세세하게 파일별로 나누면 이 컴포넌트가 어디에서 사용되는지 확실히 알기 어렵습니다. export를 하게 되니 컴포넌트 끼리 서로 import 할 수도 있고, 다른 페이지에서도 import 할 수 있게됩니다.
DetailPage.tsx 파일 아래쪽에 컴포넌트를 배치하면 해당 컴포넌트 들이 DetailPage에서만 사용됨이 보장됩니다.
const DetailPage: React.FC = () => { ... };
export default DetailPage;
// 이 컴포넌트 들은 DetailPage.tsx안에서만 사용됨이 보장됩니다.
const Self = () => { ... };
const Main = () => { ... };
const Sidebar = () => { ... };
const Loading = () => { ... };
const Error = () => { ... };
const MemberList = () => { ... };
const StudyList = () => { ... };
const TodoList = () => { ... };
폴더 구조가 단순해집니다
지금은 얼마 없지만 DetailPage안에 이런 저런 컴포넌트가 많아지고 이것들을 전부 다른 파일로 분리하면 폴더 구조가 길어집니다.
// 하위 컴포넌트가 많아지면 detail-page/components 하위에 폴더가 길어집니다
📦detail-page
┣ 📂components
┃ ┣ 📂error
┃ ┣ 📂loading
┃ ┣ 📂main
┃ ┣ 📂member-list
┃ ┣ 📂sidebar
┃ ┣ 📂study-list
┃ ┗ 📂todo-list
┣ 📜.DS_Store
┗ 📜DetailPage.tsx
반면, DetailPage.tsx아래에 두게 되면 폴더 구조가 심플해집니다.
문제점
응집성은 높아지지만, 해당 페이지의 컴포넌트가 많아지면 아래쪽이 너무 늘어나서 컴포넌트간의 구분이 어려워 지고 가독성이 떨어집니다. 네이밍 충돌도 빈번하게 일어날 수 있습니다.
const DetailPage: React.FC = () => { ... };
export default DetailPage;
const Self = () => { ... };
const Main = () => { ... };
const Sidebar = () => { ... };
const Loading = () = { ... };
const Error = () = { ... };
// MemberList 관련
const MemberList = () => { ... };
const MemberListItem = () => { ... };
const AddMemberButton = () => { ... };
const RemoveMemberButton = () => { ... };
// StudyList 관련
const StudyList = () => { ... };
const StudyListItem = () => { ... };
const AddStudyButton = () => { ... };
const RemoveStudyButton = () => { ... };
// TodoList 관련
const TodoList = () => { ... };
const TodoListItem = () => { ... };
const AddTodoItemButton = () => { ... };
const RemoveTodoItemButton = () => { ... };
그래서 우선 아래쪽에 구현을 하고, 때가 되면 파일로 분리하는 것이 좋다고 생각합니다.
대표적으로,
- 컴포넌트가 복잡하고 무거울때
- 스토리북에 표현해야할 필요가 있을때
- 테스트가 필요한 컴포넌트일때
위 세가지 경우에 컴포넌트를 아래쪽이 아닌 같은 디렉토리의 독립적인 폴더에 두는것이 좋다고 생각합니다.
이후에 page 컴포넌트 아래쪽에는 다음과 같은 컴포넌트들이 남습니다.
- 가벼운 컴포넌트들
- Error
- Loading
- AddButton
- PageTitle
- Layout관련 컴포넌트들
- Main
- Sidebar
결과적으로 페이지 안에서 컴포넌트의 분리는 획일적인 방식 보다는 적당히 개발자가 덜 피곤한 방향으로 선택하는것이 좋다고 생각합니다.
네이밍 충돌
위처럼 아래쪽에 놓다보면 네이밍에 고민이 생길 때가 있습니다.
const Form = () => { ... };
export default Form;
const NameField = () => {
return (
...
<NameInput />
);
};
const NameInput = () => {
return <input />
};
const AgeField = () => {
return (
...
<AgeInput />
);
};
const AgeInput = () => {
return <input />
};
Input을 구분하기 위해서 prefix를 계속 붙여줘야 합니다. 이런 경우에는 class와 css props를 활용할 수 있습니다.
const NameField = () => {
const style = css`
...
.name-label {}
.name-input {}
`;
return (
<div css={style}>
<label className='name-label' />
<input className='name-input' />
</div>
);
};
name-label과 name-input이 외부에 노출(외부 css에 의해 영향을 받을 수 있음)되는 문제가 있지만, global css를 사용하지 않거나 BEM을 사용하면 해결할 수 있습니다.
page내부에서 분리된 컴포넌트
page내부에서 분리된 컴포넌트들도 마찬가지로 가급적 같은 파일의 아래쪽에 하위 컴포넌트들을 배치합니다.
// src/pages/detail-page/components/member-list/MemberList.tsx
const MemberList = () => { ... };
export default MemberList;
const Self = () => { ... };
const ListItem = () => { ... };
const AddButton = () => { ... };
const RemoveButton = () => { ... };
마찬가지로 너무 뚱뚱한 하위 컴포넌트는 다른 파일로 분리할 수 있습니다.
다만, 페이지와는 달리 하위 폴더로 만들기 보다는 이름을 잘 줘서 같은 계층에 폴더를 배치합니다.
📦detail-page
┣ 📂components
┃ ┣ 📂member-list
┃ ┣ 📂member-list-item // member-list 하위에 두지 않습니다!
┃ ┣ 📂...
┣ 📜.DS_Store
┗ 📜DetailPage.tsx
폴더 구조를 깊게 가져가면, 분류는 확실히 된다는 장점이 있지만 폴더 가독성이 떨어진다고 생각하기 때문입니다.
공통 컴포넌트 = UI Library
공통 컴포넌트는 ui library를 만든다는 관점으로 접근합니다.
공통 컴포넌트는 도메인에 관련된 내용이 없고 오로지 모양과 general한 기능을 가지고 있는 컴포넌트 입니다.
예를들어서,
📦components
┣ 📂@shared
┃ ┣ 📂avatar
┃ ┣ 📂button
┃ ┃ ┣ 📂box-button
┃ ┃ ┣ 📂icon-button
┃ ┃ ┣ 📂linked-button
┃ ┃ ┣ 📂text-button
┃ ┃ ┣ 📂toggle-button
┃ ┃ ┣ 📂unstyled-button
┃ ┃ ┗ 📜index.tsx
┃ ┣ 📂button-group
┃ ┣ 📂card
┃ ┣ 📂center
┃ ┣ 📂checkbox
┃ ┣ 📂chip
┃ ┣ 📂divider
┃ ┣ 📂drop-down-box
┃ ┣ 📂flex
┃ ┣ 📂form
┃ ┣ 📂icons
┃ ┃ ┣ 📂bookmark-icon
┃ ┃ ┣ 📂crown-icon
┃ ┃ ┣ 📂down-arrow-icon
┃ ┃ ┣ 📂folder-icon
┃ ┃ ┗ 📜index.tsx
┃ ┣ 📂image
┃ ┣ 📂infinite-scroll
┃ ┣ 📂input
┃ ┣ 📂label
┃ ┣ 📂letter-counter
┃ ┣ 📂list-item
┃ ┣ 📂markdown-render
┃ ┣ 📂meta-box
┃ ┣ 📂modal
┃ ┣ 📂multi-tag-select
┃ ┣ 📂page-title
┃ ┣ 📂page-wrapper
┃ ┣ 📂pagination
┃ ┣ 📂route-with-condition
┃ ┣ 📂section-title
┃ ┣ 📂select
┃ ┣ 📂textarea
┃ ┗ 📂user-info-item
┗ 📜.DS_Store
이런 식으로 도메인 정보가 없고 모양만 있는 컴포넌트를 @shared에 배치합니다.
페이지간 공통으로 사용되는 컴포넌트
페이지간에 공통으로 사용되는 컴포넌트는 어떨까요? 이 경우에는 2가지 케이스로 나뉩니다.
도메인이 같은 경우

도메인 정보가 들어가면서 여러 페이지 사이에서 중복되어 사용되는 컴포넌트인 경우에는 최상단 components 폴더에 배치합니다.
예를들어, study-chip 같은 경우 여러 페이지에서 사용 되고 그 의미도 동일하기 때문에 src/components
에 배치합니다.
📦components
┣ 📂@shared
┃ ┣ 📂...
┣ 📂study-chip
도메인이 다른데 모양이 같은 경우

이러한 경우에는 도메인을 제외한 모양만을 기준으로 공통 컴포넌트로 분리할 수 있는지 노력해보고, 어렵다면 그냥 중복을 허용합니다.
정리
- 가능하면 같은 파일 내에 하위 컴포넌트를 배치합니다.
- 때에 따라서 다른 파일로 컴포넌트를 분리합니다.
- 공통 컴포넌트는 도메인 정보가 없이 모양만을 기준으로 만들고,
src/components/@shared
에 배치합니다. - 페이지간 공유되는 컴포넌트는
src/components
에 배치합니다. - 핵심은 일관되고 완벽한 구조가 아닌, 개발자가 덜 피곤한 구조를 만드는것 입니다.
이상입니다. 읽어주셔서 감사합니다 😀