Menu

TypeScript Exclude, Extract, NonNullable 사용법

Exclude, Extract, NonNullable은 유니언 타입의 멤버를 걸러냅니다. 각각 무엇을 남기고 무엇을 제거하는지, 모양으로 유니언 멤버를 고르는 방법, 조건부 타입으로 만들어진 원리, Omit, Pick과의 차이를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

Exclude<T, U>는 유니언 타입에서 멤버를 제거하고, Extract<T, U>는 일치하는 멤버만 남기며, NonNullable<T>는 null과 undefined를 제거합니다. 셋 다 유니언을 걸러낼 뿐 객체 속성은 건드리지 않습니다.

다른 모든 유틸리티 타입처럼 이들도 컴파일 시점에만 존재합니다. 마지막 줄은 컴파일러가 거부하며, 실행되는 JavaScript는 Visible이 무엇인지 전혀 모릅니다.

Exclude, Extract, NonNullable 한눈에 보기

타입남기는 것예시결과
Exclude<T, U>U에 대입할 수 없는 T의 멤버Exclude<"a" | "b" | "c", "a">"b" | "c"
Extract<T, U>U에 대입할 수 있는 T의 멤버Extract<"a" | "b" | "c", "a" | "z">"a"
NonNullable<T>null과 undefined를 뺀 전부NonNullable<string | null | undefined>string

핵심 표현은 "U에 대입 가능한"입니다. U가 정확한 멤버 목록일 필요는 없습니다. 여러 멤버가 들어맞는 더 넓은 타입이어도 됩니다.

모양으로 유니언 멤버 고르기

구별된 유니언에서 부분적인 모양을 넘긴 Extract는 그 모양을 가진 변형을 뽑아냅니다. 직접 작성하지 않은 유니언의 변형 하나에 이름을 붙이는 흔한 방법입니다.

모양 { type: "click" }은 click 변형만의 상위 타입이므로 Extract는 그것만 남깁니다. 여러 변형에 들어맞는 모양이라면(예를 들어 두 변형이 { x: number }를 가졌다면) 모두 남깁니다.

NonNullable

NonNullable<T>는 !== null && !== undefined 검사의 타입 버전입니다. 선택적인 것에서 타입을 파생할 때 등장합니다.

User["manager"]는 { name: string } | null | undefined이고(?가 undefined를 더합니다), NonNullable은 둘 다 벗겨 냅니다. TypeScript 5.5부터는 컴파일러가 그 타입 서술어를 스스로 추론하므로 raw.filter((s) => s != null)은 이미 string[]으로 타입 지정됩니다. 명시적인 s is NonNullable<typeof s>는 의도를 드러내 주고 이전 버전에서도 동작합니다. 정의는 type NonNullable<T> = T & {}입니다. 빈 객체 타입 {}는 null과 undefined를 뺀 모든 값을 받으므로, 이것과 교차시키면 정확히 그 둘만 제거됩니다.

만들어진 원리

Exclude와 Extract는 표준 라이브러리에 있는 한 줄짜리 조건부 타입입니다.

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

T가 유니언이면 조건부 타입은 각 멤버에 따로 적용되고(분배), never 멤버는 결과에서 사라집니다. 그래서 Exclude<"a" | "b", "a">는 ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), 즉 never | "b", 즉 "b"가 됩니다. 분배는 조건부 타입 페이지에서 자세히 다룹니다.

Exclude vs Omit, Extract vs Pick

두 쌍 모두 무언가를 "제거"하거나 "남기기" 때문에 이름이 헷갈리기 쉽습니다. 차이는 무엇에 동작하느냐입니다.

대상제거하거나 남기는 것예시
Exclude<T, U>유니언유니언 멤버Exclude<"a" | "b", "a"> → "b"
Extract<T, U>유니언유니언 멤버Extract<"a" | "b", "a"> → "a"
Omit<T, K>객체 타입속성Omit<User, "password">
Pick<T, K>객체 타입속성Pick<User, "name">

객체 타입에 Exclude를 쓰는 것은 흔한 실수이며, 조용히 실패합니다.

Account는 "password"에 대입할 수 없으므로 Exclude는 그것을 통째로 남깁니다. Omit 자체는 키에 적용한 Exclude로 만들어져 있습니다. Omit<T, K>는 Pick<T, Exclude<keyof T, K>>입니다. 자세한 내용은 Omit을 보세요.

더 엄격한 Exclude

Exclude의 두 번째 매개변수는 어떤 타입이든 받으므로, 오타는 아무것도 제거하지 않고 오류도 내지 않습니다. 제약을 추가하면 컴파일러가 제외하려는 것이 정말 유니언에 있는지 검사합니다.

index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.

철자를 고치면 컴파일되고, Strict는 "published" | "archived"와 같아집니다. 같은 방법이 Extract와 엄격한 Omit(K extends keyof T)에도 통합니다.

자주 묻는 질문

TypeScript에서 Exclude는 무엇을 하나요?

Exclude<T, U>는 유니언 T에서 U에 대입 가능한 멤버를 모두 제거합니다. Exclude<"a" | "b" | "c", "a">는 "b" | "c"입니다. 여러 멤버를 제거하려면 U로 유니언을 넘기세요: Exclude<T, "a" | "b">.

Exclude와 Extract의 차이는 무엇인가요?

서로 반대입니다. Exclude<T, U>는 T의 멤버 중 U에 대입할 수 없는 것을 남기고, Extract<T, U>는 대입할 수 있는 것만 남깁니다. 같은 T와 U에 대해 두 결과를 합치면 T가 됩니다.

Exclude와 Omit의 차이는 무엇인가요?

Exclude는 유니언 타입에 동작하며 유니언 멤버를 제거합니다. Omit은 객체 타입에 동작하며 속성을 제거합니다. Exclude<User, "password">는 쓸모 있는 일을 하지 않고(User를 반환합니다), Omit<User, "password">는 그 속성이 없는 객체 타입을 줍니다. Omit은 Exclude로 만들어져 있어, keyof T에서 키를 제외합니다.

TypeScript에서 NonNullable은 무엇을 하나요?

NonNullable<T>는 타입에서 null과 undefined를 제거합니다. NonNullable<string | null | undefined>는 string입니다. {}가 null과 undefined를 뺀 모든 값을 받으므로 T & {}로 정의되어 있습니다.

존재하지 않는 멤버를 넘겨도 Exclude가 오류를 내지 않는 이유는 무엇인가요?

두 번째 매개변수에 제약이 없어서 어떤 타입이든 받기 때문에, Exclude<Status, "drafft"> 같은 오타는 조용히 아무것도 제거하지 않습니다. 더 엄격한 버전 type StrictExclude<T, U extends T> = Exclude<T, U>를 정의하면 컴파일러가 U가 T의 일부인지 검사합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기