타입스크립트 유틸리티 타입(Utility Types) 자세히 보기

타입스크립트는 타입을 변환하고 조작하는 데 사용할 수 있는 다양한 내장 유틸리티 타입을 제공합니다.
이를 통해 코드의 재사용성을 높이고 더 간결하며 가독성 좋은 코드를 작성할 수 있습니다.
유틸리티 이름 | 설명 (대표 타입) | 타입 변수 |
---|---|---|
Partial |
TYPE 의 모든 속성을 선택적으로 변경한 새로운 타입 반환 (인터페이스) |
<TYPE> |
Required |
TYPE 의 모든 속성을 필수로 변경한 새로운 타입 반환 (인터페이스) |
<TYPE> |
Readonly |
TYPE 의 모든 속성을 읽기 전용으로 변경한 새로운 타입 반환 (인터페이스) |
<TYPE> |
Record |
KEY 를 속성으로, TYPE 를 그 속성값의 타입으로 지정하는 새로운 타입 반환 (인터페이스) |
<KEY, TYPE> |
Pick |
TYPE 에서 KEY 로 속성을 선택한 새로운 타입 반환 (인터페이스) |
<TYPE, KEY> |
Omit |
TYPE 에서 KEY 로 속성을 생략하고 나머지를 선택한 새로운 타입 반환 (인터페이스) |
<TYPE, KEY> |
Exclude |
TYPE1 에서 TYPE2 를 제외한 새로운 타입 반환 (유니언) |
<TYPE1, TYPE2> |
Extract |
TYPE1 에서 TYPE2 를 추출한 새로운 타입 반환 (유니언) |
<TYPE1, TYPE2> |
NonNullable |
TYPE 에서 null 과 undefined 를 제외한 새로운 타입 반환 (유니언) |
<TYPE> |
Parameters |
TYPE 의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플) |
<TYPE> |
ConstructorParameters |
TYPE 의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플) |
<TYPE> |
ReturnType |
TYPE 의 반환 타입을 새로운 타입으로 반환 (함수) |
<TYPE> |
InstanceType |
TYPE 의 인스턴스 타입을 반환 (클래스) |
<TYPE> |
ThisParameterType |
TYPE 의 명시적 this 매개변수 타입을 새로운 타입으로 반환 (함수) |
<TYPE> |
OmitThisParameter |
TYPE 의 명시적 this 매개변수를 제거한 새로운 타입을 반환 (함수) |
<TYPE> |
ThisType |
TYPE 의 this 컨텍스트(Context)를 명시, 별도 반환 없음! (인터페이스) |
<TYPE> |
Awaited |
Promise 의 resolve 타입을 반환 |
<TYPE> |
NoInfer |
제네릭 타입 T 에 대한 타입 추론을 차단 |
<TYPE> |
Uppercase |
문자열 리터럴 TYPE 의 모든 문자를 대문자로 변환 |
<TYPE> |
Lowercase |
문자열 리터럴 TYPE 의 모든 문자를 소문자로 변환 |
<TYPE> |
Capitalize |
문자열 리터럴 TYPE 의 첫 글자를 대문자로 변환 |
<TYPE> |
Uncapitalize |
문자열 리터럴 TYPE 의 첫 글자를 소문자로 변환 |
<TYPE> |
# Partial
TYPE
의 모든 속성을 선택적(?
)으로 변경한 새로운 타입을 반환합니다.
1Partial<TYPE>
123456789101112interface User { name: string age: number } const userA: User = { // TS2741: Property 'age' is missing in type '{ name: string }' but required in type 'User'. name: 'A' } const userB: Partial<User> = { name: 'B' }
위 예제의 Partial<User>
은 다음과 같이 이해할 수 있습니다.
1234interface NewType { name?: string age?: number }
# Required
TYPE
의 모든 속성을 필수로 변경한 새로운 타입을 반환합니다.
1Required<TYPE>
123456789101112interface User { name?: string age?: number } const userA: User = { name: 'A' } const userB: Required<User> = { // TS2741: Property 'age' is missing in type '{ name: string }' but required in type 'Required<User>'. name: 'B' }
위 예제의 Required<User>
은 다음과 같이 이해할 수 있습니다.
1234interface User { name: string age: number }
# Readonly
TYPE
의 모든 속성을 읽기 전용(readonly
)으로 변경한 새로운 타입을 반환합니다.
'인터페이스 > 읽기 전용 속성' 파트를 참고하세요.
1Readonly<TYPE>
12345678910111213141516interface User { name: string age: number } const userA: User = { name: 'A', age: 12 } userA.name = 'AA' // OK! const userB: Readonly<User> = { name: 'B', age: 13 } userB.name = 'BB' // TS2540: Cannot assign to 'name' because it is a read-only property.
위 예제의 Readonly<User>
는 다음과 같이 이해할 수 있습니다.
1234interface NewType { readonly name: string readonly age: number }
# Record
KEY
를 속성(Key)으로, TYPE
를 그 속성의 값 타입(Type)으로 지정하는 새로운 객체 타입을 반환합니다.
1Record<KEY, TYPE>
123456type Name = 'neo' | 'lewis' const developers: Record<Name, number> = { neo: 12, lewis: 13 }
위 예제의 Record<Name, number>
는 다음과 같이 이해할 수 있습니다.
1234interface NewType { neo: number lewis: number }
Record
는 엄격하지 않은 객체 타입을 지정하는 경우에 유용합니다.
1234567891011function getValues(obj: Record<string, unknown>, keys: string[]) { return keys.map(key => obj[key]) } const user = { name: 'Neo', age: 22, isValid: true, email: 'neo@gmail.com' } console.log(getValues(user, ['name', 'email'])) // ['Neo', 'neo@gmail.com']
# Pick
TYPE
에서 KEY
로 속성을 선택한 새로운 객체 타입을 반환합니다.
유니언 타입으로 여러 속성을 선택할 수 있습니다.
1Pick<TYPE, KEY>
12345678910111213interface User { name: string age: number email: string isValid: boolean } type Key = 'name' | 'email' const user: Pick<User, Key> = { name: 'Neo', email: 'thesecon@gmail.com', age: 22 // TS2322: Type '{ name: string; email: string; age: number; }' is not assignable to type 'Pick<User, Key>'. }
위 예제의 Pick<User, Key>
은 다음과 같이 이해할 수 있습니다.
1234interface NewType { name: string email: string }
# Omit
위에서 살펴본 Pick
과 반대로, TYPE
에서 KEY
로 속성을 생략하고 나머지를 선택한 새로운 객체 타입을 반환합니다.
유니언 타입으로 여러 속성을 생략할 수 있습니다.
1Omit<TYPE, KEY>
12345678910111213interface User { name: string age: number email: string isValid: boolean } type Key = 'name' | 'email' const user: Omit<User, Key> = { age: 22, isValid: true, name: 'Neo' // TS2322: Type '{ age: number; isValid: true; name: string; }' is not assignable to type 'Pick<User, "age" | "isValid">'. }
위 예제의 Omit<User, Key>
은 다음과 같이 이해할 수 있습니다.
123456interface NewType { // name: string age: number // email: string isValid: boolean }
# Exclude
유니언 TYPE1
에서 유니언 TYPE2
를 제외한 새로운 타입을 반환합니다.
1Exclude<TYPE1, TYPE2>
1234567type T = string | number | boolean | string[] const a: Exclude<T, number | string[]> = 'Only string' const b: Exclude<T, number> = 1234 // Type Error! ts(2322) const c: T = 'Hello world!' const d: T = 1234 console.log(a, b, c, d)
# Extract
유니언 TYPE1
에서 유니언 TYPE2
를 추출한 새로운 타입을 반환합니다.
1Extract<TYPE1, TYPE2>
1234567type T = string | number | string[] type U = number | boolean // Extract<T, U> is number const a: Extract<T, U> = 123 const b: Extract<T, U> = 'Only number' // Type Error! ts(2322) console.log(a, b)
# NonNullable
유니언 TYPE
에서 null
과 undefined
를 제외한 새로운 타입을 반환합니다.
1NonNullable<TYPE>
1234type T = string | number | undefined const a: T = undefined const b: NonNullable<T> = null // TS2322: Type 'null' is not assignable to type 'string | number'.
# Parameters
함수 TYPE
의 매개변수 타입을 새로운 튜플(Tuple) 타입으로 반환합니다.
1Parameters<TYPE>
12345678910function fn(a: string | number, b: boolean) { console.log(a, b) } // Parameters<typeof fn> is [string | number, boolean] const a: Parameters<typeof fn> = ['Hello', true] const b: Parameters<typeof fn> = ['Hello', true] const c: Parameters<typeof fn> = [123, false] const d: Parameters<typeof fn> = [123, 456] // Type Error! ts(2322) console.log(a, b, c, d)
# ConstructorParameters
클래스 TYPE
의 매개변수 타입을 새로운 튜플 타입으로 반환합니다.
1ConstructorParameters<TYPE>
1234567891011121314class User { constructor( public name: string, private age: number ) {} } // ConstructorParameters<typeof User> is [string, number] const a: ConstructorParameters<typeof User> = ['Neo', 12] const b: ConstructorParameters<typeof User> = ['Lewis'] // Type Error! ts(2741) console.log(a, b) const neo = new User('Neo', 12) const lewis = new User('Lewis', 34) console.log(neo, lewis)
# ReturnType
함수 TYPE
의 반환(Return) 타입을 새로운 타입으로 반환합니다.
1ReturnType<TYPE>
123456function fn(str: string) { return str } const a: ReturnType<typeof fn> = 'Only string' const b: ReturnType<typeof fn> = 1234 // TS2322: Type '123' is not assignable to type 'string'.
# InstanceType
클래스 TYPE
의 인스턴스 타입을 반환합니다.
1InstanceType<TYPE>
12345class User { constructor(public name: string) {} } const neo: InstanceType<typeof User> = new User('Neo')
# ThisParameterType
함수 TYPE
의 명시적 this
매개변수 타입을 새로운 타입으로 반환합니다.
함수 TYPE
에 명시적 this
매개변수가 없는 경우 알 수 없는 타입(Unknown)을 반환합니다.
'함수 > this > 명시적 this' 파트를 참고하세요.
1ThisParameterType<TYPE>
다음 예제에서 함수 toHex
의 명시적 this
타입은 Number
이고, 그 타입을 참고해서 함수 numberToString
의 매개변수 n
의 타입을 선언합니다.
따라서 toHex
에 다른 타입의 this
가 바인딩 되는 것을 방지할 수 있습니다.
123456789// https://www.typescriptlang.org/docs/handbook/utility-types.html#thisparametertype function toHex(this: Number) { return this.toString(16) } function numberToString(n: ThisParameterType<typeof toHex>) { return toHex.apply(n) }
# OmitThisParameter
함수 TYPE
의 명시적 this
매개변수를 생략(제거)한 새로운 타입을 반환합니다.
1OmitThisParameter<TYPE>
다음 예제에서 데이터 cat
을 기준으로 설계한 함수 getAge
는 다른 타입을 가지는 새로운 데이터 dog
를 this
로 사용할 수 없습니다.
하지만 OmitThisParameter
를 통해 명시적 this
를 생략한 새로운 타입의 함수를 만들 수 있기 때문에, getAge
를 직접 수정하지 않고 데이터 dog
를 사용할 수 있습니다.
123456789101112131415161718// 기존 객체 const cat = { age: 12 // Number } function getAge(this: typeof cat) { return this.age } getAge.call(cat) // 12 // 새로운 객체 const dog = { age: '13' // String } getAge.call(dog) // Type Error! ts(2345) // this를 생략한 새로운 함수 타입 const getAgeForDog: OmitThisParameter<typeof getAge> = getAge getAgeForDog.call(dog) // 13
# ThisType
TYPE
의 this
컨텍스트(Context)를 명시하고 별도의 타입을 반환하지 않습니다.
1ThisType<TYPE>
함수 makeNeo
의 인수로 사용되는 메소드 getName
은 내부에서 this.name
을 사용하고 있기 때문에 ThisType
을 통해 명시적으로 this
컨텍스트를 설정합니다.
다만, ThisType
은 별도의 타입을 반환하지 않기 때문에 makeNeo
반환 값({ name: 'Neo', ...methods }
)에 대한 타입이 정상적으로 추론(Inference)되지 않습니다.
따라서 as User
와 같이 따로 타입을 단언(Assertions)해야 neo.getName
을 정상적으로 호출할 수 있습니다.
123456789101112131415interface User { name: string getName: () => string } function makeNeo(methods: ThisType<User>) { return { name: 'Neo', ...methods } as User } const neo = makeNeo({ getName() { return this.name } }) neo.getName() // Neo
# Awaited
Awaited
유틸리티 타입은 Promise
의 이행(Resolved) 타입을 반환합니다.Promise
가 중첩된 경우 재귀적으로 이행 타입을 찾아 반환합니다.
1Awaited<TYPE>
1234567891011// A is string type A = Awaited<Promise<string>> // B is number type B = Awaited<Promise<Promise<number>>> // C is string | number type C = Awaited<Promise<string> | Promise<number>> // D is string | number type D = Awaited<Promise<string> | number>
대표적으로 API 응답 데이터의 타입을 얻을 때 유용합니다.
예를 들어, 다음과 같이 API로 사용자 정보를 반환(Promise<User>
)하는 비동기 함수가 있습니다.
123456789interface User { id: number name: string email: string } export async function fetchUser(id: number): Promise<User> { const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`) return res.json() }
fetchUser
함수에서 반환하는 사용자 데이터의 타입을 Awaited 유틸리티 타입으로 얻을 수 있습니다.type fetchUser
로 함수의 타입을 얻고, ReturnType
으로 반환 타입을 얻은 후, Awaited
로 이행 타입을 얻습니다.
이렇게 하면 API 함수(fetchUser
)의 반환값이 바뀌더라도 User
타입이 자동으로 업데이트되므로 유지보수가 쉬워집니다.
1234567891011import { fetchUser } from '@/api/user' // `fetchUser` 함수의 반환 타입은 `Promise<User>`입니다. type User = Awaited<ReturnType<typeof fetchUser>> function getUserName(user: User) { return user.name } const user = await fetchUser(1) getUserName(user)
# NoInfer
NoInfer
유틸리티 타입은 제네릭 타입 변수의 추론이 의도치 않게 확장되는 것을 차단합니다.
이렇게 하면 특정 위치에서 타입을 추론하는 것을 차단하고 다른 위치에서 지정된 타입에 의존하도록 할 수 있습니다.
1NoInfer<TYPE>
다음 예제의 getItem
함수는 읽기전용 배열과 기본 아이템을 전달 받습니다.
그리고 배열에 기본 아이템(defaultItem
)이 포함되어 있으면 그 기본 아이템을 반환하고, 포함되어 있지 않으면 기본 아이템 대신 배열의 첫 번째 아이템을 반환합니다.
이 함수는 런타임에서 문제 없이 잘 동작하지만, 두 번째 인수가 배열 아이템에 포함되어 있지 않아도 특별히 타입 에러는 발생하지 않습니다.getItem(colors, 'blue')
호출과 같이 colors
에 포함되지 않은 blue
값을, defaultItem
의 타입 변수 T
를 통해 "red" | "yellow" | "green" | "blue"
으로 확장해서 추론하기 때문입니다.
12345678function getItem<T>(items: readonly T[], defaultItem?: T) { if (!defaultItem) return items[0] return items.includes(defaultItem) ? defaultItem : items[0] } const colors = ['red', 'yellow', 'green'] as const console.log(getItem(colors, 'yellow')) // 'yellow' console.log(getItem(colors, 'blue')) // 'red'
이번에는 두 번째 인수가 배열 아이템에 포함되어 있지 않을 때 타입 에러가 발생하도록 NoInfer
유틸리티 타입을 사용해 보겠습니다.defaultItem
의 타입 변수 T
를 통해서 타입을 확장 추론하지 않도록 NoInfer<T>
로 수정합니다.
그러면 두 번째 인수가 배열 아이템에 포함되어 있지 않을 때 타입 에러가 발생합니다.
1234567891011function getItem<T>(items: readonly T[], defaultItem?: NoInfer<T>) { // ... } const colors = ['red', 'yellow', 'green'] as const console.log(getItem(colors, 'yellow')) // 'yellow' console.log(getItem(colors, 'blue')) // Type Error! const numbers = [7, 8, 9] as const console.log(getItem(numbers, 8)) // 8 console.log(getItem(numbers, 100)) // Type Error!
# Uppercase
TYPE
의 모든 문자를 대문자로 변환한 새로운 타입을 반환합니다.
1Uppercase<TYPE>
1234567type Str = `hello ${string}!` type UpperStr = Uppercase<Str> const str: Str = 'hello world!' const upperStr1: UpperStr = 'HELLO WORLD!' const upperStr2: UpperStr = 'HELLO world!' // Type Error! console.log(str, upperStr1, upperStr2)
# Lowercase
TYPE
의 모든 문자를 소문자로 변환한 새로운 타입을 반환합니다.
1Lowercase<TYPE>
1234567type Str = `HELLO ${string}!` type LowerStr = Lowercase<Str> const str: Str = 'HELLO WORLD!' const lowerStr1: LowerStr = 'hello world!' const lowerStr2: LowerStr = 'HELLO world!' // Type Error! console.log(str, lowerStr1, lowerStr2)
# Capitalize
문자열 리터럴 TYPE
의 첫 글자를 대문자로 변환한 새로운 타입을 반환합니다.
1Capitalize<TYPE>
1234567type Str = `hello ${string}!` type CapStr = Capitalize<Str> const str: Str = 'hello world!' const capStr1: CapStr = 'Hello world!' const capStr2: CapStr = 'hello world!' // Type Error! console.log(str, capStr1, capStr2)
# Uncapitalize
문자열 리터럴 TYPE
의 첫 글자를 소문자로 변환한 새로운 타입을 반환합니다.
1234567type Str = `HELLO ${string}!` type UncapStr = Uncapitalize<Str> const str: Str = 'HELLO WORLD!' const uncapStr1: UncapStr = 'hELLO WORLD!' const uncapStr2: UncapStr = 'HELLO WORLD!' // Type Error! console.log(str, uncapStr1, uncapStr2)
끝까지 읽어주셔서 감사합니다.
좋아요와 응원 댓글은 블로그 운영에 큰 힘이 됩니다!