📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄

[TypeScript] 타입을 조작하는 4가지 방법 (인덱스, keyof, 맵드)

인덱스드 액세스 타입(Indexed Access)

interface User {
    id: number;
    name: string;
    profile: {
        job: string;
        mbti: string;
    }
}

const userA: User = {
    id: 0,
    name: 'Lee',
    profile: {
        job: 'developer',
        mbti: 'INFJ'
    }
}

// 타입의 특정 인덱스 타입을 가져오고 싶을 때 ['인덱스']로 가져올 수 있다.
function getUserProfile(profile: User['profile']) {
    console.log(`${profile.job}-${profile.mbti}`);
}

getUserProfile({job: 'designer', mbti: 'ENFP'});

인덱스드 액세스 타입은 타입스크립트에서 객체 타입 내부에 있는 특정 프로퍼티의 타입을 동적으로 가져오는 방법을 제공한다. 객체에서 프로퍼티에 접근할 때 obj['name']과 같이 문자열 리터럴을 사용하여 프로퍼티 값을 가져왔듯이, 타입에서도 이와 유사하게 사용할 수 있다.

 

위 코드를 보면 getUserProfile() 함수는 User 타입의 profile 프로퍼티 부분의 타입만 필요한 상황이다. 일일이 {job: string, mbti: string} 이라고 적는 것보다는 인덱스를 이용해서 선언된 객체 타입의 프로퍼티 타입만 쏙 가져오는 게 더 효율적이다.

만약, 객체 안에 객체 안에 객체처럼 여러 개의 객체가 겹쳐있을 경우 ['profile']['A']['B'].. 처럼 인덱스를 중첩하여 쓰는 것도 가능하다.

 

keyof 연산자

interface Person {
    name: string;
    age: number;
}

let personA: Person = {
    name: 'Kim',
    age: 29
}

// key의 타입에는 원래 'name' | 'age'가 들어가야 함.
function getPerson(person: Person, key: keyof Person) {
    console.log(person[key]);
}

keyof는 객체 타입의 key를 유니온 타입으로 추출하는 연산자이다. 위 코드에서 보면 getPerson() 함수는 person 객체의 key 값을 구하는 함수이다. 따라서, key 매개변수의 타입에는 person 객체의 key('name' | 'age')가 들어가야 한다.

예시 코드에서는 'name', 'age' 2개뿐이지만, 만약 구해야 하는 프로퍼티의 개수가 1000개라면 이걸 일일이 모두 입력하고 있어야 할까?

 

이때, 객체 타입의 key를 쉽게 추출하는 역할이 바로 keyof 연산자이다. keyof 연산자를 사용하면 객체 타입의 모든 프로퍼티 key들을 유니온 타입으로 간편하게 얻을 수 있다. 이렇게 얻은 유니온 타입은 해당 객체의 프로퍼티 key들을 표현하므로 key 변수에 이러한 타입을 할당하여 프로퍼티 key에 대한 타입 검사를 수행할 수 있다.

 

keyof 연산자는 객체 타입의 key를 유니온 타입으로 추출해 주며, 프로퍼티의 key가 필요한 상황에서 사용하면 유용하다.

let person = {
    name: 'Lee',
    age: 30
}

type Person = typeof person;

typeof를 타입으로 사용할 수도 있다. 타입을 정의하는데 typeof를 사용하면 특정 변수의 타입을 뽑아내는 용도로 쓸 수 있다. 즉, 객체 타입이나 인터페이스를 생성하지 않아도 객체에 대한 타입을 생성할 수 있는 것이다.

 

맵드 타입(Mapped Type)

interface User {
    id?: number;
    name?: string;
    age?: number;
}

// key 자리에 id, name, age가 차례대로 들어감
type User2 = {
    [key in 'id' | 'name' | 'age']?: User[key];
}

// 값이 모두 boolean인 객체 타입을 만들 수도 있음
type BooleanUser = {
    [key in 'id' | 'name' | 'age']: boolean;
}

// keyof를 활용하면 더 쉽게 사용할 수 있음
type TypeUser = {
    [key in keyof User]: User[key];
}

// User 자리에 User2가 와도 됨
function updateUser(user: User) {
    age: 30;
}

맵드 타입유니온 타입을 key로 받아서 객체 타입을 생성해 주는 역할을 한다. [key in 'id' | 'name']과 같은 형식으로 in 뒤에 있는 유니온 타입이 객체 타입의 key로 들어간다. 이미 선언된 객체 타입을 복사하거나(특정 프로퍼티를 선택적 프로퍼티로 만들고 싶거나) 아니면 값이 모두 같은 프로퍼티를 가진 객체 타입을 만들어주고 싶을 때 사용한다.

 

유니온 타입에 keyof 연산자를 활용하면 맵드 타입을 더 쉽게 구현할 수 있다.

맵드 타입은 인터페이스로는 구현되지 않기 때문에 타입 별칭으로만 사용해야 한다는 점만 주의하자. (interface로 선언하면 안 되고 type으로 해줘야 한다는 뜻)

 

템플릿 리터럴 타입

type Color = 'red' | 'blue' | 'green';
type Animal = 'dog' | 'cat' | 'horse';
type ColorAnimal = `${Color}-${Animal}`;

const colorAnimal: ColorAnimal = 'red-dog';

템플릿 리터럴 타입은 여러 가지 문자열 리터럴 타입을 조합하거나 문자열을 이용해서 무언가를 표현해야 할 때 사용되는 타입이다.

 

위의 예시 코드를 보면 ColorAnimal에 각각 3개씩 문자 리터럴 타입이 선언되어 있다. 그리고 ColorAnimal 타입에는 템플릿 리터럴을 이용해서 두 문자열을 조합해주고 있다. 이렇게 되면 ColorAnimal 타입에는 'red-dog', 'red-cat', 'blue-dog' 등 총 9가지의 조합이 나올 수 있게 된다. (문자열 리터럴 타입을 이용해서 들어올 수 있는 문자열을 제한하는 것)

템플릿 리터럴은 많이 사용되지는 않지만, 문자열로 여러 가지 상황들을 표현해야 하는 경우 유용하게 사용될 수 있다.

 

1. 인덱스드 액세스 : 타입 객체의 특정 프로퍼티에 접근. User['profile']
2. keyof 연산자 : 객체 타입의 key를 유니온 타입으로 가져옴. keyof User
3. typeof 연산자를 타입으로 사용하면 객체를 객체 타입으로 만들어 줌. type Person = typeof person
4. 맵드 타입 : 유니온 타입을 key로 받아서 객체 타입을 생성함. [key in keyof User]: User[key]