LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의 사용법이다

2026년 7월 7일·6분 읽기
범위: Effective TypeScript 2판 Item 67–73
오늘의 질문: “타입이 맞으면 라이브러리/컴포넌트 API도 쓰기 쉬운 걸까?”

먼저 보는 코드

ts
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 타입 경계

틀리기 쉬운 직감: export된 타입은 내부 타입을 재사용하면 된다

타입 재사용은 좋은 습관처럼 보입니다. 하지만 public API에서는 “중복 제거”보다 “사용자가 붙잡을 계약”이 더 중요할 때가 많습니다.

ts
type ButtonVariant = "primary" | "secondary";

type ButtonProps = {
  variant?: ButtonVariant;
  children: React.ReactNode;
  onClick?: () => void;
};

이렇게 공개 타입을 작게 만들면 내부 state는 자유롭게 바꿀 수 있습니다. 반대로 외부 사용자가 의존해야 하는 값은 타입에 분명히 드러납니다.

리뷰 질문은 이렇습니다.

이 타입은 구현을 설명하는가, 사용자가 의존할 계약을 설명하는가?

TSDoc은 타입이 말하지 못하는 의도를 보강한다

타입은 값의 모양을 잘 말하지만, 의도·단위·부작용·주의점까지 모두 말하지는 못합니다.

ts
/**
 * 서버에 저장된 초 단위 만료 시간을 받아 남은 시간을 계산한다.
 * 클라이언트 시계와 서버 시계가 다를 수 있으므로 표시 용도로만 사용한다.
 */
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를 반환함

callback의 this는 일부러 설계해야 한다

자바스크립트 라이브러리 API에서는 callback의 this가 계약이 될 수 있습니다. 타입스크립트에서는 가짜 첫 번째 매개변수처럼 this 타입을 적을 수 있습니다.

ts
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은 경계가 분명할 때만 쓴다

기존 라이브러리 타입을 확장해야 할 때 module augmentation을 쓸 수 있습니다.

ts
declare module "express-session" {
  interface SessionData {
    userId?: string;
  }
}

이런 코드는 편합니다. 하지만 전역처럼 적용되므로 위치와 의도가 흐리면 프로젝트 전체 타입 환경을 조용히 바꿉니다. 특히 프론트엔드에서는 라우터, theme, test matcher, global window 확장에서 자주 보입니다.

리뷰 질문은 “확장이 필요한가?”보다 한 단계 더 들어갑니다.

확장 파일 이름과 위치가 찾기 쉬운가? 런타임에도 그 값이 실제로 들어가는가? 테스트/스토리북/서버 환경에서도 같은 전역 타입을 가정하지 않는가? 라이브러리 업그레이드 후 확장이 깨질 가능성을 알고 있는가?

타입스크립트 기능은 팀의 디버깅 비용까지 포함해 선택한다

타입스크립트에는 enum, namespace, decorator, parameter property처럼 자바스크립트로 변환될 때 런타임 코드를 만들거나 문법 해석 부담을 주는 기능도 있습니다.

ts
enum Status {
  Idle,
  Loading,
  Done,
}

이 코드가 항상 나쁘다는 뜻은 아닙니다. 다만 프론트엔드 앱에서는 번들 출력, tree-shaking, Babel/SWC/Vite 설정, 문서 예제의 이해 난도를 함께 봐야 합니다. 단순 union으로 충분하다면 런타임 출력이 없는 타입을 고르는 편이 더 예측 가능할 수 있습니다.

ts
type Status = "idle" | "loading" | "done";

리뷰 질문은 “이 기능을 쓸 수 있는가?”가 아니라 “이 팀과 빌드 파이프라인에서 이 기능이 가장 단순한 선택인가?”입니다.

source map은 운영 디버깅 계약이다

타입스크립트는 브라우저가 그대로 실행하는 코드가 아닙니다. 번들러와 트랜스파일러를 거쳐 실행됩니다. 운영 에러를 원래 TS/TSX 파일 위치로 추적하려면 source map 정책이 필요합니다.

json
{
  "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를 어떻게 이해하고, 어떤 값에 의존하며, 운영에서 어떻게 디버깅할지를 정하는 인터페이스입니다. 공개 경계에서는 내부 구현 편의보다 사용자 계약을 먼저 봐야 합니다.

Day 12 타입 리뷰 퀴즈

Quiz1 / 4
Q.public API 타입을 만들 때 내부 state 타입을 그대로 export하는 것이 위험할 수 있는 이유는 무엇일까요?

Post Q&A

오케이징에게 물어보기

이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의 사용법이다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/effective-typescript
파일 12개, 폴더 0개
이펙티브 타입스크립트 2판 Day 1: 타입스크립트를 믿기 전에 알아야 할 경계이펙티브 타입스크립트 2판 Day 2: 타입을 값의 집합으로 보기이펙티브 타입스크립트 2판 Day 3: 타입 반복을 줄이는 리뷰 감각이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성 패턴이펙티브 타입스크립트 2판 Day 5: 타입 추론을 방해하지 않는 API 설계이펙티브 타입스크립트 2판 Day 6: 유효한 상태만 표현하는 타입 설계이펙티브 타입스크립트 2판 Day 7: 문자열보다 도메인 의미를 좁히기이펙티브 타입스크립트 2판 Day 8: any를 밖에 세우고 unknown으로 검증하기이펙티브 타입스크립트 2판 Day 9: 제네릭은 관계를 보존할 때만 강하다이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고 빠지는 분기는 막는다이펙티브 타입스크립트 2판 Day 11: 객체 타입은 열려 있고, 계약은 닫아야 한다이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의 사용법이다

같은 섹션의 대표 이미지

12 posts · latest first
내부 구현 타입에서 문서화된 public API 타입으로 넘어가는 경계 다이어그램
Study26. 07. 08.

이펙티브 타입스크립트 2판 Day 12: 타입은 공개 API의.

Item 67–73 범위를 바탕으로 public API 타입, TSDoc, this callback, module augmentation, TypeScript 기능 선택, source map을 코드 리뷰 관점에서 정리합니다.

26. 07. 08.SEOJing
Object iteration, Record, tuple/rest, XOR, brand, @types 버전 관리를 연결한 타입 리뷰 다이어그램
Study26. 07. 07.

이펙티브 타입스크립트 2판 Day 11: 객체 타입은 열려.

Item 60–66 범위를 바탕으로 Object iteration, Record, tuple/rest, XOR, brand, @types 버전 경계를 코드 리뷰에서 다루는 방법을 정리합니다.

26. 07. 07.SEOJing
타입 계약, 타입 테스트, never exhaustiveness, 코드 생성 경계를 보여주는 다이어그램
Study26. 07. 06.

이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고.

Item 55–59 범위를 바탕으로 type test, 타입 표시, 꼬리 재귀 한계, 코드 생성, never exhaustiveness를 코드 리뷰에서 다루는 방법을 정리합니다.

26. 07. 06.SEOJing
외부 경계, 제네릭, 조건부 타입, 템플릿 리터럴 타입의 역할과 과한 타입 계산의 위험을 보여주는 다이어그램
Study26. 07. 05.

이펙티브 타입스크립트 2판 Day 9: 제네릭은 관계를 보존할.

Item 49–54 범위를 바탕으로 type coverage, 제네릭, 조건부 타입, 템플릿 리터럴 타입을 코드 리뷰에서 어떻게 판단할지 정리합니다.

26. 07. 05.SEOJing
외부 JSON을 unknown으로 받고 검증 뒤 도메인 타입으로 넘기는 경계와 any가 내부로 번지는 위험을 보여주는 다이어그램
Study26. 07. 04.

이펙티브 타입스크립트 2판 Day 8: any를 밖에 세우고.

Item 42–48 범위를 바탕으로 any, unknown, 타입 단언, monkey patching, soundness 함정을 외부 입력 경계와 코드 리뷰 관점에서 정리합니다.

26. 07. 04.SEOJing
넓은 문자열과 선택적 필드를 도메인 이름과 유효 상태 유니온으로 좁히는 다이어그램
Study26. 06. 29.

이펙티브 타입스크립트 2판 Day 7: 문자열보다 도메인.

Item 35–41 범위를 바탕으로 string 남용, optional 필드, 특수 값, 도메인 이름 설계를 코드 리뷰 관점에서 정리합니다.

26. 06. 29.SEOJing
API 응답을 유효한 상태 유니온으로 변환하는 흐름 다이어그램
Study26. 06. 28.

이펙티브 타입스크립트 2판 Day 6: 유효한 상태만 표현하는.

Item 28–34 범위를 바탕으로 추론 위치, API 경계, null 처리, union 설계를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 28.SEOJing
타입스크립트 추론이 값에서 API 경계로 흐르는 과정을 보여주는 다이어그램
Study26. 06. 27.

이펙티브 타입스크립트 2판 Day 5: 타입 추론을 방해하지 않는.

Effective TypeScript 2판 Item 19–22를 바탕으로 타입 추론, 타입 넓히기, 함수형 기법, 유니온 설계를 코드 리뷰 관점에서 정리합니다.

26. 06. 27.SEOJing
TypeScript에서 넓은 타입으로 먼저 담으면 key와 literal 정보가 사라지고 리뷰 체크가 약해지는 흐름 다이어그램
Study26. 06. 26.

이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성.

Effective TypeScript 2판의 Item 16–21을 바탕으로 index signature, 타입 추론 기본, 변수/객체 생성 패턴을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 26.SEOJing
반복된 타입 선언을 원본 타입에서 파생한 타입으로 바꿔 리뷰 체크를 강하게 만드는 흐름 다이어그램
Study26. 06. 25.

이펙티브 타입스크립트 2판 Day 3: 타입 반복을 줄이는 리뷰.

Effective TypeScript 2판의 Item 11–15를 바탕으로 excess property check, 함수식 타입, type vs interface, readonly, 타입 반복 제거를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 25.SEOJing
TypeScript 타입을 값의 집합, type space, value space, assertion 경계로 정리한 다이어그램
Study26. 06. 24.

이펙티브 타입스크립트 2판 Day 2: 타입을 값의 집합으로.

Effective TypeScript 2판의 Item 6–10을 바탕으로 타입 시스템을 탐색하는 법, 타입을 값의 집합으로 보는 관점, type/value space, 타입 단언의 경계를 코드 리뷰 관점에서 정리합니다.

26. 06. 24.SEOJing
TypeScript를 JavaScript 위의 정적 타입 계층, 타입 제거, 구조적 타이핑, any의 구멍으로 정리한 다이어그램
Study26. 06. 23.

이펙티브 타입스크립트 2판 Day 1: 타입스크립트를 믿기.

Effective TypeScript 2판의 Item 1–5를 바탕으로 TypeScript와 JavaScript의 관계, tsconfig, 타입 제거, 구조적 타이핑, any의 위험을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 23.SEOJing