99

최근 30분간 동시 방문자 수를 표시합니다. (~)

최고 동시 방문자 수 -
어제: 0명 / 오늘: 0명

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

타입스크립트 유틸리티 타입(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에서 nullundefined를 제외한 새로운 타입 반환 (유니언) <TYPE>
Parameters TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플) <TYPE>
ConstructorParameters TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플) <TYPE>
ReturnType TYPE의 반환 타입을 새로운 타입으로 반환 (함수) <TYPE>
InstanceType TYPE의 인스턴스 타입을 반환 (클래스) <TYPE>
ThisParameterType TYPE의 명시적 this 매개변수 타입을 새로운 타입으로 반환 (함수) <TYPE>
OmitThisParameter TYPE의 명시적 this 매개변수를 제거한 새로운 타입을 반환 (함수) <TYPE>
ThisType TYPEthis 컨텍스트(Context)를 명시, 별도 반환 없음! (인터페이스) <TYPE>
Awaited Promiseresolve 타입을 반환 <TYPE>
NoInfer 제네릭 타입 T에 대한 타입 추론을 차단 <TYPE>
Uppercase 문자열 리터럴 TYPE의 모든 문자를 대문자로 변환 <TYPE>
Lowercase 문자열 리터럴 TYPE의 모든 문자를 소문자로 변환 <TYPE>
Capitalize 문자열 리터럴 TYPE의 첫 글자를 대문자로 변환 <TYPE>
Uncapitalize 문자열 리터럴 TYPE의 첫 글자를 소문자로 변환 <TYPE>

# Partial

TYPE의 모든 속성을 선택적(?)으로 변경한 새로운 타입을 반환합니다.

TS
content_copy
1
Partial<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
interface 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>은 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
interface NewType { name?: string age?: number }

# Required

TYPE의 모든 속성을 필수로 변경한 새로운 타입을 반환합니다.

TS
content_copy
1
Required<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
interface 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>은 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
interface User { name: string age: number }

# Readonly

TYPE의 모든 속성을 읽기 전용(readonly)으로 변경한 새로운 타입을 반환합니다.

'인터페이스 > 읽기 전용 속성' 파트를 참고하세요.

TS
content_copy
1
Readonly<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface 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>는 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
interface NewType { readonly name: string readonly age: number }

# Record

KEY를 속성(Key)으로, TYPE를 그 속성의 값 타입(Type)으로 지정하는 새로운 객체 타입을 반환합니다.

TS
content_copy
1
Record<KEY, TYPE>
TS
content_copy
1
2
3
4
5
6
type Name = 'neo' | 'lewis' const developers: Record<Name, number> = { neo: 12, lewis: 13 }

위 예제의 Record<Name, number>는 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
interface NewType { neo: number lewis: number }

Record는 엄격하지 않은 객체 타입을 지정하는 경우에 유용합니다.

TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
function 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로 속성을 선택한 새로운 객체 타입을 반환합니다.
유니언 타입으로 여러 속성을 선택할 수 있습니다.

TS
content_copy
1
Pick<TYPE, KEY>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
interface 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>은 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
interface NewType { name: string email: string }

# Omit

위에서 살펴본 Pick과 반대로, TYPE에서 KEY로 속성을 생략하고 나머지를 선택한 새로운 객체 타입을 반환합니다.
유니언 타입으로 여러 속성을 생략할 수 있습니다.

TS
content_copy
1
Omit<TYPE, KEY>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
interface 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>은 다음과 같이 이해할 수 있습니다.

TS
content_copy
1
2
3
4
5
6
interface NewType { // name: string age: number // email: string isValid: boolean }

# Exclude

유니언 TYPE1에서 유니언 TYPE2를 제외한 새로운 타입을 반환합니다.

TS
content_copy
1
Exclude<TYPE1, TYPE2>
TS
content_copy
1
2
3
4
5
6
7
type 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를 추출한 새로운 타입을 반환합니다.

TS
content_copy
1
Extract<TYPE1, TYPE2>
TS
content_copy
1
2
3
4
5
6
7
type 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에서 nullundefined를 제외한 새로운 타입을 반환합니다.

TS
content_copy
1
NonNullable<TYPE>
TS
content_copy
1
2
3
4
type 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) 타입으로 반환합니다.

TS
content_copy
1
Parameters<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
function 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의 매개변수 타입을 새로운 튜플 타입으로 반환합니다.

TS
content_copy
1
ConstructorParameters<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class 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) 타입을 새로운 타입으로 반환합니다.

TS
content_copy
1
ReturnType<TYPE>
TS
content_copy
1
2
3
4
5
6
function 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의 인스턴스 타입을 반환합니다.

TS
content_copy
1
InstanceType<TYPE>
TS
content_copy
1
2
3
4
5
class User { constructor(public name: string) {} } const neo: InstanceType<typeof User> = new User('Neo')

# ThisParameterType

함수 TYPE의 명시적 this 매개변수 타입을 새로운 타입으로 반환합니다.
함수 TYPE에 명시적 this 매개변수가 없는 경우 알 수 없는 타입(Unknown)을 반환합니다.

'함수 > this > 명시적 this' 파트를 참고하세요.

TS
content_copy
1
ThisParameterType<TYPE>

다음 예제에서 함수 toHex의 명시적 this 타입은 Number이고, 그 타입을 참고해서 함수 numberToString의 매개변수 n의 타입을 선언합니다.
따라서 toHex에 다른 타입의 this가 바인딩 되는 것을 방지할 수 있습니다.

TS
content_copy
1
2
3
4
5
6
7
8
9
// 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 매개변수를 생략(제거)한 새로운 타입을 반환합니다.

TS
content_copy
1
OmitThisParameter<TYPE>

다음 예제에서 데이터 cat을 기준으로 설계한 함수 getAge는 다른 타입을 가지는 새로운 데이터 dogthis로 사용할 수 없습니다.
하지만 OmitThisParameter를 통해 명시적 this를 생략한 새로운 타입의 함수를 만들 수 있기 때문에, getAge를 직접 수정하지 않고 데이터 dog를 사용할 수 있습니다.

warning

이제 this.age에는 어떤 값도 들어갈 수 있음을 주의하세요!

TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 기존 객체 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

TYPEthis 컨텍스트(Context)를 명시하고 별도의 타입을 반환하지 않습니다.

TS
content_copy
1
ThisType<TYPE>

함수 makeNeo의 인수로 사용되는 메소드 getName은 내부에서 this.name을 사용하고 있기 때문에 ThisType을 통해 명시적으로 this 컨텍스트를 설정합니다.
다만, ThisType은 별도의 타입을 반환하지 않기 때문에 makeNeo 반환 값({ name: 'Neo', ...methods })에 대한 타입이 정상적으로 추론(Inference)되지 않습니다.
따라서 as User와 같이 따로 타입을 단언(Assertions)해야 neo.getName을 정상적으로 호출할 수 있습니다.

TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface 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가 중첩된 경우 재귀적으로 이행 타입을 찾아 반환합니다.

TS
content_copy
1
Awaited<TYPE>
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
// 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>)하는 비동기 함수가 있습니다.

/api/user.ts
TS
content_copy
1
2
3
4
5
6
7
8
9
interface 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 타입이 자동으로 업데이트되므로 유지보수가 쉬워집니다.

/main.ts
TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
import { 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 유틸리티 타입은 제네릭 타입 변수의 추론이 의도치 않게 확장되는 것을 차단합니다.
이렇게 하면 특정 위치에서 타입을 추론하는 것을 차단하고 다른 위치에서 지정된 타입에 의존하도록 할 수 있습니다.

TS
content_copy
1
NoInfer<TYPE>

다음 예제의 getItem 함수는 읽기전용 배열과 기본 아이템을 전달 받습니다.
그리고 배열에 기본 아이템(defaultItem)이 포함되어 있으면 그 기본 아이템을 반환하고, 포함되어 있지 않으면 기본 아이템 대신 배열의 첫 번째 아이템을 반환합니다.
이 함수는 런타임에서 문제 없이 잘 동작하지만, 두 번째 인수가 배열 아이템에 포함되어 있지 않아도 특별히 타입 에러는 발생하지 않습니다.
getItem(colors, 'blue') 호출과 같이 colors에 포함되지 않은 blue 값을, defaultItem의 타입 변수 T를 통해 "red" | "yellow" | "green" | "blue"으로 확장해서 추론하기 때문입니다.

TS
content_copy
1
2
3
4
5
6
7
8
function 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>로 수정합니다.
그러면 두 번째 인수가 배열 아이템에 포함되어 있지 않을 때 타입 에러가 발생합니다.

TS
content_copy
1
2
3
4
5
6
7
8
9
10
11
function 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의 모든 문자를 대문자로 변환한 새로운 타입을 반환합니다.

TS
content_copy
1
Uppercase<TYPE>
TS
content_copy
1
2
3
4
5
6
7
type 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의 모든 문자를 소문자로 변환한 새로운 타입을 반환합니다.

TS
content_copy
1
Lowercase<TYPE>
TS
content_copy
1
2
3
4
5
6
7
type 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의 첫 글자를 대문자로 변환한 새로운 타입을 반환합니다.

TS
content_copy
1
Capitalize<TYPE>
TS
content_copy
1
2
3
4
5
6
7
type 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의 첫 글자를 소문자로 변환한 새로운 타입을 반환합니다.

TS
content_copy
1
2
3
4
5
6
7
type 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)