이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의.
Item 67–73 범위를 바탕으로 public API 타입, TSDoc, this callback, module augmentation, TypeScript 기능 선택, source map을 코드 리뷰 관점에서 정리합니다.
범위: Effective TypeScript 2판 Item 67–73
오늘의 질문: “타입이 맞으면 라이브러리/컴포넌트 API도 쓰기 쉬운 걸까?”
type InternalButtonState = {
id: string;
mountedAt: number;
analyticsKey?: string;
variant: "primary" | "secondary";
};
export type ButtonProps = InternalButtonState & {
children: React.ReactNode;
onClick?: () => void;
};동작은 될 수 있습니다. 하지만 이 타입은 내부 구현 상태까지 public props로 새어 나갑니다. 사용하는 쪽은 id, mountedAt, analyticsKey가 정말 필요한 값인지 헷갈립니다. 나중에 내부 구현을 바꾸고 싶어도 이미 공개된 타입이 발목을 잡습니다.
타입 재사용은 좋은 습관처럼 보입니다. 하지만 public API에서는 “중복 제거”보다 “사용자가 붙잡을 계약”이 더 중요할 때가 많습니다.
type ButtonVariant = "primary" | "secondary";
type ButtonProps = {
variant?: ButtonVariant;
children: React.ReactNode;
onClick?: () => void;
};이렇게 공개 타입을 작게 만들면 내부 state는 자유롭게 바꿀 수 있습니다. 반대로 외부 사용자가 의존해야 하는 값은 타입에 분명히 드러납니다.
리뷰 질문은 이렇습니다.
이 타입은 구현을 설명하는가, 사용자가 의존할 계약을 설명하는가?
타입은 값의 모양을 잘 말하지만, 의도·단위·부작용·주의점까지 모두 말하지는 못합니다.
/**
* 서버에 저장된 초 단위 만료 시간을 받아 남은 시간을 계산한다.
* 클라이언트 시계와 서버 시계가 다를 수 있으므로 표시 용도로만 사용한다.
*/
export function getRemainingSeconds(expiresAtEpochSeconds: number): number {
return expiresAtEpochSeconds - Math.floor(Date.now() / 1000);
}number라는 타입만 보면 millisecond인지 second인지 모릅니다. 함수 이름으로 어느 정도 추측할 수 있지만, 공개 API라면 문서가 계약 일부가 됩니다.
좋은 TSDoc은 타입을 반복하지 않습니다.
| 약한 문서 | 더 나은 문서 |
|---|---|
id는 string입니다 | GitHub user id이며 URL slug가 아님 |
timeout은 number입니다 | millisecond 단위, 0이면 재시도 없음 |
callback 함수입니다 | 실패해도 throw하지 말고 false를 반환함 |
자바스크립트 라이브러리 API에서는 callback의 this가 계약이 될 수 있습니다. 타입스크립트에서는 가짜 첫 번째 매개변수처럼 this 타입을 적을 수 있습니다.
type VisitCallback = (this: HTMLElement, event: MouseEvent) => void;
function onVisit(element: HTMLElement, callback: VisitCallback) {
element.addEventListener("click", function (event) {
callback.call(element, event);
});
}React 컴포넌트 코드에서는 보통 callback this를 쓰지 않는 편이 자연스럽습니다. 하지만 DOM 래퍼, 플러그인, 레거시 라이브러리 어댑터를 만들 때는 “this를 제공하는 API인지, this를 쓰면 안 되는 API인지”를 타입으로 드러내야 합니다.
리뷰에서는 다음을 봅니다.
callback 문서와 실제 호출 방식이 일치하는가?
arrow function을 넘겨도 되는 API인가, this 바인딩이 필요한 API인가?
this 타입을 숨긴 채 any로 통과시키고 있지 않은가?
기존 라이브러리 타입을 확장해야 할 때 module augmentation을 쓸 수 있습니다.
declare module "express-session" {
interface SessionData {
userId?: string;
}
}이런 코드는 편합니다. 하지만 전역처럼 적용되므로 위치와 의도가 흐리면 프로젝트 전체 타입 환경을 조용히 바꿉니다. 특히 프론트엔드에서는 라우터, theme, test matcher, global window 확장에서 자주 보입니다.
리뷰 질문은 “확장이 필요한가?”보다 한 단계 더 들어갑니다.
확장 파일 이름과 위치가 찾기 쉬운가? 런타임에도 그 값이 실제로 들어가는가? 테스트/스토리북/서버 환경에서도 같은 전역 타입을 가정하지 않는가? 라이브러리 업그레이드 후 확장이 깨질 가능성을 알고 있는가?
타입스크립트에는 enum, namespace, decorator, parameter property처럼 자바스크립트로 변환될 때 런타임 코드를 만들거나 문법 해석 부담을 주는 기능도 있습니다.
enum Status {
Idle,
Loading,
Done,
}이 코드가 항상 나쁘다는 뜻은 아닙니다. 다만 프론트엔드 앱에서는 번들 출력, tree-shaking, Babel/SWC/Vite 설정, 문서 예제의 이해 난도를 함께 봐야 합니다. 단순 union으로 충분하다면 런타임 출력이 없는 타입을 고르는 편이 더 예측 가능할 수 있습니다.
type Status = "idle" | "loading" | "done";리뷰 질문은 “이 기능을 쓸 수 있는가?”가 아니라 “이 팀과 빌드 파이프라인에서 이 기능이 가장 단순한 선택인가?”입니다.
타입스크립트는 브라우저가 그대로 실행하는 코드가 아닙니다. 번들러와 트랜스파일러를 거쳐 실행됩니다. 운영 에러를 원래 TS/TSX 파일 위치로 추적하려면 source map 정책이 필요합니다.
{
"compilerOptions": {
"sourceMap": true
}
}하지만 source map은 공개 범위도 고려해야 합니다. 외부에 배포되는 앱에서 소스가 그대로 노출되는 형태인지, 에러 수집 도구에만 업로드하는지, 민감한 경로/주석이 들어가지 않는지 확인해야 합니다.
코드 리뷰에서는 배포 설정까지 같이 봅니다.
운영 에러를 원본 파일/라인으로 추적할 수 있는가? public source map을 노출해도 되는 프로젝트인가? 에러 수집 도구에 업로드 후 공개 파일은 제거하는 전략인가? 번들러 설정과 TS 설정이 서로 다른 source map 정책을 만들지 않는가?
| 변화 | 확인할 질문 |
|---|---|
export type 추가 | 내부 구현 타입이 public contract로 새고 있지 않은가? |
| TSDoc 추가 | 타입을 반복하지 않고 단위/의도/제약을 설명하는가? |
| callback 타입 | this 계약이 실제 호출 방식과 맞는가? |
| module augmentation | 전역 타입 확장이 찾기 쉽고 런타임 주입과 일치하는가? |
| enum/namespace/decorator 사용 | 빌드 출력과 팀 디버깅 비용까지 감당할 이유가 있는가? |
| source map 설정 | 운영 추적성과 소스 노출 정책이 함께 검토됐는가? |
이번 범위의 핵심은 “타입을 더 많이 적는 것”이 아닙니다. 타입은 사용자가 API를 어떻게 이해하고, 어떤 값에 의존하며, 운영에서 어떻게 디버깅할지를 정하는 인터페이스입니다. 공개 경계에서는 내부 구현 편의보다 사용자 계약을 먼저 봐야 합니다.
Post Q&A
이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의 사용법이다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
Item 60–66 범위를 바탕으로 Object iteration, Record, tuple/rest, XOR, brand, @types 버전 경계를 코드 리뷰에서 다루는 방법을 정리합니다.