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

Contact Me

© 2026 SEOJing. All rights reserved.

TypeScript프론트엔드Effective TypeScript타입 추론코드 리뷰

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

2026년 6월 26일·11분 읽기

오늘의 범위

Day 4는 Effective TypeScript 2판의 Item 16–21을 묶어서 읽습니다.

text
Item 16–21: index signature, inference 기본, 변수/객체 생성 패턴

오늘의 질문은 이겁니다.

text
타입을 어디에 써야 추론이 더 강해지는가?

TypeScript를 처음 쓰면 모든 변수에 타입을 붙이고 싶어집니다. 하지만 실제 리뷰에서는 반대 상황이 많습니다. 너무 일찍 넓은 타입을 붙이면 TypeScript가 이미 알고 있던 literal 정보, key 이름, undefined 가능성을 잃습니다. 반대로 아무 타입도 안 쓰면 API 경계가 흐려집니다.

Day 4는 “타입을 쓰지 말자”가 아니라 “추론이 잘하는 일과 annotation이 필요한 일을 나누자”는 감각입니다.


먼저 지도를 그려보자

TypeScript에서 넓은 타입으로 먼저 담으면 key와 literal 정보가 사라지고 리뷰 체크가 약해지는 흐름

추론을 살리는 기본 흐름은 단순합니다.

  1. 값 생성 지점에서는 가능한 한 구체적인 정보를 유지한다.
  2. 외부 API, 함수 입력, 저장소 경계에서는 명시적인 타입으로 계약을 세운다.
  3. 넓은 index signature나 any는 마지막 수단으로 둔다.
  4. 객체를 만든 뒤 나중에 필드를 붙이는 패턴은 가능하면 피한다.

이 네 가지가 지켜지면 타입 선언이 줄어도 체크는 더 강해질 수 있습니다.


흔한 착각: index signature는 편한 객체 타입이다

다음 타입은 편해 보입니다.

ts
type Labels = {
  [key: string]: string;
};

const labels: Labels = {
  home 

Post Q&A

오케이징에게 물어보기

이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성 패턴 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/effective-typescript
파일 4개, 폴더 0개
이펙티브 타입스크립트 2판 Day 1: 타입스크립트를 믿기 전에 알아야 할 경계이펙티브 타입스크립트 2판 Day 2: 타입을 값의 집합으로 보기이펙티브 타입스크립트 2판 Day 3: 타입 반복을 줄이는 리뷰 감각이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성 패턴

같은 섹션의 대표 이미지

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

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

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

26. 06. 26.SEOJing
:
"홈"
,
blog: "블로그",
};

문제는 key가 너무 넓다는 점입니다.

ts
labels.home; // string
labels.admin; // string으로 보일 수 있음

admin이라는 key가 실제로 없는데도 타입만 보면 string처럼 보입니다. 런타임에서는 undefined일 수 있습니다. 이런 타입은 “아무 문자열 key나 가능하다”는 강한 선언입니다. 단순히 “여러 key가 있는 객체”라는 뜻이 아닙니다.

가능하면 key 집합을 좁히는 편이 낫습니다.

ts
type Page = "home" | "blog" | "about";
type Labels = Record<Page, string>;

const labels: Labels = {
  home: "홈",
  blog: "블로그",
  about: "소개",
};

이제 빠진 key와 잘못된 key를 TypeScript가 잡을 수 있습니다. 리뷰 질문은 이렇게 바뀝니다.

text
정말 모든 string key를 허용해야 하는가?
가능한 key 목록이 도메인에 이미 존재하지 않는가?

추론은 생각보다 많이 안다

값을 바로 만들면 TypeScript는 꽤 많은 정보를 압니다.

ts
const status = "idle";
// type: "idle"

let nextStatus = "idle";
// type: string

const는 값이 바뀌지 않으므로 literal type으로 좁힐 수 있습니다. let은 나중에 다른 문자열이 들어올 수 있으니 string으로 넓힙니다. 객체도 비슷합니다.

ts
const request = {
  method: "GET",
  retry: 2,
};

// method는 보통 string으로 넓어질 수 있음

이 객체를 fetch 옵션처럼 더 구체적인 계약에 맞춰야 한다면, 값 생성 지점에서 의도를 드러내야 합니다.

ts
const request = {
  method: "GET",
  retry: 2,
} as const;

또는 실제 경계 타입에 맞춰 검사하게 할 수 있습니다.

ts
const request = {
  method: "GET",
  retry: 2,
} satisfies {
  method: "GET" | "POST";
  retry: number;
};

satisfies는 값의 세부 타입을 최대한 보존하면서도 특정 shape를 만족하는지 검사할 수 있어, 설정 객체나 라우트 테이블 리뷰에 특히 좋습니다.


annotation이 추론을 약하게 만들 때

모든 변수에 타입을 붙이면 안전해 보입니다.

ts
const routes: Record<string, string> = {
  home: "/",
  blog: "/blog",
};

하지만 이렇게 쓰면 home과 blog라는 구체 key가 사라지고 “문자열 key를 가진 string dictionary”가 됩니다. 다음 코드도 타입상 어색하지 않아 보일 수 있습니다.

ts
routes.admn; // 오타인데 타입이 놓치기 쉬움

반대로 객체 자체의 정보를 살리면 오타를 더 잘 잡습니다.

ts
const routes = {
  home: "/",
  blog: "/blog",
} as const;

type RouteName = keyof typeof routes;

function navigate(name: RouteName) {
  location.href = routes[name];
}

navigate("admn"); // 에러

annotation은 필요합니다. 다만 “정보를 보존한 뒤 경계를 만들 것인지”, “처음부터 넓은 타입으로 덮어버릴 것인지”를 구분해야 합니다.


객체를 나중에 조립하는 패턴의 비용

JavaScript에서는 이런 식의 점진적 조립이 흔합니다.

ts
const article = {};
article.slug = raw.slug;
article.title = raw.title;
article.description = raw.summary ?? "";

TypeScript에서는 시작점이 {}라서 문제가 됩니다. 어떤 필드가 언제 생기는지 추론하기 어렵고, 결국 assertion이나 any로 밀어붙이기 쉽습니다.

더 좋은 기본형은 한 번에 객체를 만드는 것입니다.

ts
const article = {
  slug: raw.slug,
  title: raw.title,
  description: raw.summary ?? "",
};

API mapper라면 반환 타입을 경계에 둡니다.

ts
type ArticleCard = {
  slug: string;
  title: string;
  description: string;
};

function toArticleCard(raw: RawArticle): ArticleCard {
  return {
    slug: raw.slug,
    title: raw.title,
    description: raw.summary ?? "",
  };
}

이 방식은 객체 생성 위치와 계약 위치가 가까워서 리뷰하기 쉽습니다. 누락된 필드, 남는 필드, nullable 처리도 return 객체에서 한눈에 드러납니다.


callback과 배열 메서드에서는 문맥 추론을 믿어도 된다

문맥이 충분한 곳에서는 타입을 반복하지 않는 편이 낫습니다.

ts
const ids = articles.map((article) => article.id);

articles가 Article[]라면 callback의 article 타입은 문맥으로 추론됩니다. 여기에 굳이 다시 타입을 붙이면 코드가 길어지고, 원본 타입이 바뀔 때 중복 수정 지점이 생깁니다.

ts
const ids = articles.map((article: Article) => article.id);

이런 annotation은 대부분 불필요합니다. 더 나쁜 경우는 틀린 타입을 붙여서 문맥 추론을 방해하는 것입니다.

리뷰에서는 이렇게 묻습니다.

text
이 annotation은 새로운 계약을 세우는가, 이미 아는 정보를 반복하는가?

새 계약이면 남깁니다. 반복이면 지워도 체크가 유지되는지 확인합니다.


프론트엔드 리뷰에서 보는 네 가지

1. dictionary가 진짜 dictionary인가

ts
type Messages = Record<string, string>;

외부 CMS처럼 임의 key가 진짜로 올 수 있다면 괜찮습니다. 하지만 라우트, 탭, status, feature flag처럼 가능한 key가 닫혀 있다면 union key나 as const 테이블이 더 안전합니다.

2. 설정 객체는 satisfies가 어울리는가

ts
const tabs = {
  home: { label: "홈" },
  code: { label: "코드" },
} satisfies Record<string, { label: string }>;

satisfies는 shape 검사를 하면서 객체 자체의 key 정보를 보존할 수 있습니다. 메뉴, 라우트, 디자인 토큰, feature flag 테이블에 자주 맞습니다.

3. mapper는 한 번에 반환하는가

ts
function map(raw: Raw): ViewModel {
  return { ... };
}

중간에 빈 객체를 만들고 assertion으로 채우는 코드보다, 최종 반환 객체를 한 번에 만들면 누락/nullable/default 처리가 보입니다.

4. annotation이 문맥 추론을 가리고 있나

배열 메서드, React props callback, 이벤트 핸들러처럼 문맥이 강한 곳에서는 불필요한 annotation이 오히려 노이즈가 됩니다. 타입을 많이 쓰는 것이 아니라, 타입이 필요한 위치에 쓰는 것이 중요합니다.


오늘의 정리

Day 4의 핵심은 “추론을 믿을 곳과 경계를 세울 곳을 나누기”입니다.

  1. index signature는 모든 string key를 허용한다는 선언이다.
  2. 닫힌 key 집합은 union key, Record<Union, T>, as const, keyof typeof로 표현하는 편이 안전하다.
  3. 너무 이른 넓은 annotation은 literal/key 정보를 지운다.
  4. 객체는 가능하면 한 번에 만들고, 함수/API 경계에서 명시 타입을 둔다.
  5. 문맥 추론이 충분한 callback에서는 반복 annotation을 줄인다.
  6. satisfies는 설정 객체처럼 “검사도 하고 세부 정보도 보존”하고 싶은 곳에 잘 맞는다.

이 감각이 생기면 TypeScript 코드 리뷰가 “타입이 있네/없네”에서 “이 타입 배치가 체크를 강하게 만들고 있나”로 바뀝니다.


참고문헌

  • Effective TypeScript 2판, Item 16–21
  • TypeScript Handbook, type inference, index signatures, keyof, typeof, satisfies
  • SEOJing/OkayJing 프론트엔드 설정 객체와 mapper 코드 리뷰 경험

오늘의 복습 퀴즈

Quiz1 / 4
Q.Record<string, string>을 라우트 테이블 타입으로 썼을 때 가장 조심해야 할 점은 무엇일까요?
반복된 타입 선언을 원본 타입에서 파생한 타입으로 바꿔 리뷰 체크를 강하게 만드는 흐름 다이어그램
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