토스 팀에서 공개한 Frontend Fundamentals라는 코드 작성 지침서를 기반해 이걸 기반으로 토스 채용 과제를 다같이 풀어보는 모의고사를 진행했다.
각자 과제를 풀고 PR을 올려 서로 코드 리뷰를 했고, GitHub Discussion에서 설계 철학을 토론했다. 그리고 유튜브 라이브 방송을 통해 토스 리드 개발자분들의 관점도 들을 수 있었는데, 질의응답 시간이 특히 유익했다.
그냥 넘기기엔 아까운 인사이트들이 많아서 정리해본다.
Suspense에 대한 관점 전환
지금까지 나는 Suspense를 "로딩 fallback UI 보여주는 용도"로만 생각했다. 필요한 곳에만 선택적으로 쓰는 것처럼.
근데 이번에 완전히 관점이 바뀌었다:
Suspense는 성공 케이스만 신경쓸 수 있게 해주는 깔대기다.
n개의 비동기 동작이 조합될 때, 각 조합마다 로딩/에러 상태를 일일이 처리하는 게 아니라, Suspense 경계 안에서는 "모든 데이터가 준비된 상황"만 다루면 된다.
// 기존: 옵셔널 전염
if (queryData?.user?.profile?.name) {
// ...
}
// Suspense 활용: 성공 케이스에만 집중
<Suspense fallback={<Loading />}>
<UserProfile name={queryData.user.profile.name} />
</Suspense>
옵셔널 체이닝(?.)이 코드 전체로 전염되는 걸 막을 수 있다는 것 또한 이점이다.
실무 팁: Component.loading 패턴
방송에서 시연해주신 센스 중 하나인데, 로딩 컴포넌트를 이렇게 분리하는 방식도 깔끔했다:
function UserProfile({ name }: Props) {
return <div>{name}님의 프로필</div>;
}
UserProfile.loading = function UserProfileLoading() {
return <div>프로필 로딩 중...</div>;
};
// 사용
<Suspense fallback={<UserProfile.loading />}>
<UserProfile name={data.name} />
</Suspense>
컴포넌트와 로딩 상태를 논리적으로 함께 관리하면서도, 코드는 깔끔하게 분리할 수 있어서 좋았다.
추상화와 단일 책임
이번 모의고사에서 가장 많이 논의된 주제 중 하나가 커스텀 훅의 책임 범위였다.
추상화와 추출(책임이 섞인 단순히 코드를 옮기는 행위)을 명확히 구분해야 한다는 점을 배웠다. 그리고 결국 핵심은:
단일 책임을 지켜 관심사별로 묶어야 한다.
// ❌ 몬스터 훅: 너무 많은 책임을 가짐
function useUserDashboard() {
const user = useUser();
const posts = usePosts();
const analytics = useAnalytics();
const notifications = useNotifications();
// 상태 관리, 부수 효과, 계산 로직 등등...
return { user, posts, analytics, notifications, ... };
}
// ✅ 관심사별로 분리
function useUserData() {
return useUser();
}
function usePostsData() {
return usePosts();
}
몬스터 훅을 사용하면:
- 단일 책임 원칙이 깨진다
- 인지 강도가 급격히 올라간다
- 확장성이 떨어진다
이 주제와 비슷하게 GitHub Discussion을 열었는데, 고수준 커스텀 래퍼 훅에 대한 좋은 토론이 오갔다.
사실 나도 예전에 비슷한 고민을 하면서 글을 쓴 적이 있는데, 이번에 그 고민이 더 명확해진 느낌이다.
컴포넌트를 예측 가능하게 만들기
컴포넌트가 자신의 역할에 충실해야 한다는 원칙도 중요했다.
// ❌ input답지 않은 props
<CustomInput
showTooltip
validateOnBlur
asyncValidation
trackAnalytics
...
/>
// ✅ input은 input답게
<Input {...inputProps} />
컴포넌트가 "input답지 않은" props를 받기 시작하면 인지 강도가 급격히 올라간다. 개발자가 해당 컴포넌트를 볼 때 "이게 input인가? 아니면 뭔가 다른 건가?" 하는 혼란이 생기는 것.
책임 단위로 추상화하면:
- 재사용성은 자연스럽게 따라온다
- UI 형태와 코드 구조가 일대일로 매핑되어 유지보수가 쉽다
JSX와 UI를 일치시켜 가독성 올리기
방송을 보면서 깨달은 게 하나 있다.
나는 title, label 같은 것도 매직넘버라고 생각해서 전부 상수로 분리했다:
// 내가 한 방식
const TITLE = "사용자 프로필";
const SUBMIT_LABEL = "저장하기";
return (
<div>
<h1>{TITLE}</h1>
<button>{SUBMIT_LABEL}</button>
</div>
);
근데 이게 오히려 이정표를 제거한 느낌이었다.
// 더 나은 방식
return (
<div>
<h1>사용자 프로필</h1>
<button>저장하기</button>
</div>
);
JSX를 읽을 때 "아, 여기 제목이 있고, 여기 버튼이 있구나"라는 걸 바로 파악할 수 있어야 하는데, 모든 텍스트를 상수로 빼버리면 코드가 추상적인 기호의 나열처럼 보인다.
물론 비즈니스 로직에서 사용되거나, 여러 곳에서 재사용되는 상수는 분리하는 게 맞다. 하지만 단순히 UI 이정표 역할만 하는 리터럴은 그대로 두는 게 가독성 측면에서 더 좋다.
아쉬웠던 점: SSOT 레퍼런스 부족
SSOT(Single Source of Truth) 관련해서 discussion을 올렸는데 반응이 없었다 ㅠㅠ
특히 도메인 로직을 설정 객체와 순수 함수로 분리하는 패턴에 대한 좋은 예시를 보고 싶었는데, 이 부분은 레퍼런스를 더 찾아봐야 할 것 같다.
핵심 원칙 3가지
방송에서 가장 강조한 원칙들:
1. 코드 보기 전에 요구사항 문서를 먼저 읽어라
이상적인 코드 형태를 머릿속으로 그려본 후에 작성 시작.
2. UI 형태와 코드 구조를 일대일로 매핑시켜라
화면 레이아웃이 곧 컴포넌트 구조가 되면 직관적.
3. 책임 단위로 추상화하면 재사용성은 따라온다
재사용을 목표로 추상화하지 말고, 단일 책임을 목표로 설계하자.
그리고 사내 스터디
이번 주 사내 프론트 스터디에서 이 내용에 대해 토론했는데, 특히 Suspense의 관점에 대해 동료분들이 관심을 가져줘서 뿌듯했다. 주말 써서 고생한 보람이 있다 굳.
내가 작성한 코드도 첨부한다. 방송에서 강조한 원칙대로 보니 고칠게 매우 많은 듯 하다. 주말에 시간 나면 좀 건드려봐야겠다
https://github.com/toss-fe-interview/frontend-fundamentals-mock-exam-1/pull/17
'<frontend>' 카테고리의 다른 글
| 클라이언트 데이터 정규화 (15) | 2025.11.15 |
|---|---|
| 주석대신 순수함수로 TODO적기 (20) | 2025.10.19 |
| 역할 기반 엑세스 제어(RBAC) (4) | 2025.09.29 |
| Single Source of Truth Config Pattern (16) | 2025.09.21 |
| 프론트엔드 검색 알고리즘 최적화(Feat. Trie) (12) | 2025.08.09 |