Omit<T, K>는 T의 속성 중 K에 나열한 키를 뺀 나머지로 객체 타입을 만드는 내장 유틸리티 타입입니다. 전형적인 용도는 password가 있는 User 타입과, 그것이 없는 공개용 버전입니다.
PublicUser는 User와 동기화된 상태로 유지됩니다. User에 필드를 추가하면 PublicUser에도 나타나고, password는 계속 빠져 있습니다. Omit은 타입에만 동작하며, 실제 객체에서 속성을 제거하는 것은 toPublic 안의 구조 분해입니다.
여러 키 제외하기
두 번째 인수는 유니언이므로 필요한 만큼 |로 키를 나열합니다.
"서버가 생성하는 필드를 뺀 타입"은 실제 코드에서 가장 흔한 Omit입니다. 모델 타입 하나를 두고, 생성 폼과 API 요청의 입력 모양은 Omit으로 파생합니다.
Omit의 정의
Omit은 다른 유틸리티 타입 두 개로 만들어져 있습니다.
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
안쪽부터 읽으세요. keyof T는 모든 키의 유니언이고, Exclude<keyof T, K>는 그 유니언에서 원하지 않는 키를 제거하며, Pick은 남은 키를 유지합니다. 따라서 Omit<User, "password">는 Pick<User, "id" | "name" | "email">입니다. Pick이 T의 키를 매핑하므로 남은 속성의 readonly와 ? 수정자는 유지됩니다.
제약에 주목하세요. K extends keyof T가 아니라 K extends keyof any입니다. 이 세부 사항이 다음 섹션의 주제입니다.
Omit은 키가 존재하는지 검사하지 않는다
keyof any는 string | number | symbol이므로 Omit은 T에 없는 키를 포함해 어떤 키든 받습니다. 오타도 컴파일되고 아무것도 제거하지 않습니다.
어디에서도 오류가 나지 않고, 비밀번호가 JSON에 담겨 나갑니다. 느슨한 제약은 TypeScript 3.5에서 Omit을 추가할 때 의도적으로 선택한 것입니다. 팀이 든 이유는 이렇습니다. 제네릭 코드에서는 제외할 키가 T에서 오지 않는 경우가 많고(두 객체를 병합하는 함수는 나머지 인수를 Omit<T1, keyof T2>로 타입 지정할 수 있습니다), Exclude도 두 번째 매개변수를 제약하지 않으며, 제약을 건 버전은 프로젝트들이 이미 직접 작성해 둔 많은 Omit 타입을 깨뜨렸을 것입니다. 일상적인 코드에서는 더 엄격한 버전이 오타를 잡아 줍니다.
index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.
중요한 것은 첫 번째 오류입니다. 키를 keyof User에 대해 검사합니다. 두 번째 오류는 그 결과입니다. 아무것도 제외되지 않았으므로 PublicUser는 여전히 password를 요구합니다. 철자를 "password"로 고치면 컴파일됩니다. 많은 코드베이스가 공용 타입 파일에 한 번 정의해 두고 Omit 대신 사용합니다.
속성 타입 덮어쓰기
인터페이스는 다른 인터페이스를 확장하면서 속성을 호환되지 않는 타입으로 바꿀 수 없습니다. interface ApiUser extends User { id: string }은 error TS2430: Interface 'ApiUser' incorrectly extends interface 'User' 오류로 실패합니다. 먼저 속성을 제외한 다음 다시 선언하세요.
타입 별칭에서는 같은 것을 교차 타입으로 씁니다: type ApiUser = Omit<User, "id"> & { id: string }. Omit 없이 교차 타입 User & { id: string }을 쓰면 id의 타입이 number & string, 즉 never가 됩니다.
유니언 타입의 Omit
Omit은 분배적이지 않습니다. 유니언에 쓰면 유니언 전체의 keyof에서 시작하는데, 여기에는 모든 멤버가 공유하는 키만 들어 있으므로 각 멤버 고유의 속성이 사라집니다.
index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.
NewShape는 { kind: "circle" | "square" }가 되었습니다. 멤버마다 따로 제외하려면 분배 조건부 타입 안에서 Omit을 적용합니다.
T extends unknown ? ... : never는 항상 참입니다. 조건부 타입이 유니언에 분배되게 하려고 있을 뿐입니다. 왜 그렇게 동작하는지는 조건부 타입 페이지에서 설명합니다. 제외 대상 타입이 구별된 유니언이라면 언제나 DistributiveOmit을 쓰세요.
중첩 속성 제외하기
Omit은 최상위 키만 보므로 Omit<Settings, "editor.wordWrap">은 아무것도 제거하지 않습니다(그리고 위에서 보았듯 경고도 없습니다). 한 단계 아래의 속성을 빼려면 부모를 제외한 뒤, 그 부모에 따로 Omit을 적용해 다시 추가합니다.
Settings["editor"]는 인덱스 접근 타입입니다. 중첩된 객체 타입에 이름을 붙여 Omit이 그 위에서 동작하게 합니다.
런타임에 속성 제거하기
Omit은 타입만 바꿉니다. 속성이 없는 객체를 만들려면 구조 분해로 떼어 내거나, 반환 타입이 Omit인 작은 제네릭 헬퍼를 작성하세요.
헬퍼는 K를 keyof T로 제약하므로 철자가 틀린 키는 호출하는 곳에서 오류가 납니다. 복사는 얕습니다. 중첩 객체는 원본과 공유됩니다.
Omit vs Pick vs Exclude
| 대상 | 남기는 것 | 예시 | 결과 | |
|---|---|---|---|---|
Omit<T, K> | 객체 타입 | K를 뺀 모든 속성 | Omit<User, "password"> | { id; name; email } |
Pick<T, K> | 객체 타입 | K에 있는 속성만 | Pick<User, "id" | "name"> | { id; name } |
Exclude<U, M> | 유니언 타입 | M에 대입할 수 없는 유니언 멤버 | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Omit과 Pick 중에서는 원본 타입이 커질 때 어떻게 되어야 하는지로 고르세요. Omit은 새 속성을 자동으로 포함하므로 "비밀 값을 뺀 전부"에 맞습니다. Pick은 새 속성을 절대 포함하지 않으므로 "정확히 이 필드들"에 맞고, 서버 밖으로 나가는 데이터에는 더 안전합니다. Pick은 키를 keyof T에 대해 검사하지만 Omit은 그렇지 않습니다.
자주 묻는 질문
TypeScript에서 Omit은 무엇을 하나요?
Omit<T, K>는 T의 속성 중 키가 K에 있는 것을 뺀 나머지로 새 객체 타입을 만듭니다. Omit<User, "password">는 password가 없는 User입니다. 바뀌는 것은 타입뿐이며, 런타임에 객체에서 아무것도 제거하지 않습니다.
TypeScript에서 여러 속성을 Omit하려면 어떻게 하나요?
두 번째 인수로 키의 유니언을 넘깁니다: Omit<User, "password" | "createdAt" | "updatedAt">. 유니언에 있는 모든 키가 제거됩니다.
Omit과 Exclude의 차이는 무엇인가요?
Omit은 객체 타입에서 속성을 제거합니다: Omit<{ a: 1; b: 2 }, "a">는 { b: 2 }입니다. Exclude는 유니언 타입에서 멤버를 제거합니다: Exclude<"a" | "b", "a">는 "b"입니다. Omit은 Exclude로 구현되어 있습니다: Pick<T, Exclude<keyof T, K>>.
존재하지 않는 키를 넘겨도 Omit이 오류를 내지 않는 이유는 무엇인가요?
키 매개변수의 제약이 keyof T가 아니라 keyof any(아무 문자열, 숫자, 심볼)이기 때문에 Omit<User, "pasword"> 같은 오타도 컴파일되고 아무것도 제거하지 않습니다. 모르는 키에 오류를 받으려면 type StrictOmit<T, K extends keyof T> = Omit<T, K>를 정의하세요.
TypeScript에서 런타임에 객체의 속성을 제거하려면 어떻게 하나요?
나머지 요소를 쓴 구조 분해를 사용합니다: const { password, ...rest } = user;. TypeScript는 rest를 password가 없는 객체, 즉 Omit<User, "password">와 같은 모양으로 타입 지정하며, 새 객체에서 속성이 실제로 사라집니다.