728x90
타입 조작
- 기본 타입이나 별칭, 인터페이스로 만든 원래 존재하던 타입들을 상황에 따라 유동적으로 다른 타입으로 변환시키는 기능
- 제네릭을 비롯해 4가지 타입 조작 기능(인덱스드 엑세스 타입, keyof 연산자, Mapped 타입, 템플릿 리터럴 타입)에 대해 알아보자.
인덱스드 엑세스 타입
- 객체, 배열, 튜플 타입으로부터 특정 프로퍼티나 특정 요소의 타입만 추출한다.
type Post = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
};
const post: Post = {
title: '제목',
content: '본문',
author: {
id: 1,
name: 'summermong',
age: 27,
},
};
- Post 타입이 있고 이를 타입으로 하는 변수 post가 있다고 할 때, post의 author만 뽑아내고 싶다고 하자.
function printAuthorInfo(author: { id: number; name: string; age: number }) {
console.log(`${author.name}-${author.id}`);
}
printAuthorInfo(post.author);
- printAuthorInfo라는 함수를 만들었고 여기에 post의 author 객체를 매개변수로 전달하려고 한다.
- 이 author 안에 들어 있는 프로퍼티들의 타입을 다 지정해주면 함수를 호출할 수 있다.
- 그런데 Post 타입의 author에 무언가 수정되거나 추가되는 경우 관련된 코드를 모두 고쳐줘야 하는 번거로움이 있다.
- 변수 post의 author를 통째로 꺼내서 쓰고 싶을 때, 아래와 같이 특정 타입만 가져오도록 인덱스드 엑세스 타입을 사용한다.
// 객체의 배열 타입
type PostList = {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}[];
// ['author']['id']처럼 이중으로 뽑아올 수 있음!
function printAuthorInfo(author: PostList[number]['author']) {
console.log(`${author.name}-${author.id}`);
}
const post: PostList[number] = {
title: '제목',
content: '본문',
author: {
id: 1,
name: 'summermong',
age: 27,
},
};
printAuthorInfo(post.author);
- PostList 타입의 특정 프로퍼티만 가져올 수 있고, 따라서 새로운 프로퍼티가 추가/삭제/변경 되어도 수정할 필요가 없다.
- 이처럼 인덱스를 이용해 특정 타입에 접근하기 때문에 인덱스드 엑세스 타입이라고 불린다.
- 주의할 점은 저기에 넣는 것은 '값'이 아니라 타입이다!
- [number]는 값이 아니라 타입임을 잊지 말자.
- PostList[number]['author']['id']처럼 []를 연속 사용할 수 있다.
튜플 타입에서는
type Tup = [number, string, boolean];
type Tup0 = Tup[0];
type Tup1 = Tup[1];
type Tup2 = Tup[2];
type TupNum = Tup[number];
- Tup[number]는 튜플 타입의 최적의 공통 타입을 가져오기 때문에, 이 경우에는 3개의 유니온 타입을 가져온다.
keyof 연산자
- 객체 타입으로부터 프로퍼티의 모든 key들을 string literal union 타입으로 추출하는 연산자
interface Person {
name: string;
age: number;
}
function getPropertyKey(person: Person, key: "name" | "age") {
return person[key];
}
const person: Person = {
name: "summermong",
age: 27,
};
- Person 객체 타입을 정의하고, 해당 타입을 갖는 변수 person을 선언한다.
- getPropertyKey 함수를 만든다.
- 이 함수는 두 개의 매개변수가 있고, 두 번째 매개변수 key에 해당하는 프로퍼티의 값을 첫 번째 매개변수 person에서 꺼내 반환한다.
- 이 때 key의 타입을 "name" | "age"로 정의했는데, 이러면 Person 타입의 프로퍼티가 추가/수정 될 때마다 바꿔야 한다.
interface Person {
name: string;
age: number;
location: string; // 추가
}
function getPropertyKey(person: Person, key: keyof Person) {
return person[key];
}
const person: Person = {
name: "summermong",
age: 27,
};
- keyof 연산자는 [keyof 타입] 형태로 사용하며 타입의 모든 프로퍼티 key를 string literal union 타입으로 추출한다.
- 그래서 keyof Person은 name | age | location이 된다.
- 단, keyof 연산자의 뒤에는 무조건 타입만 와야 한다. (값, 변수에 사용하려고 하면 오류 발생)
with typeof 연산자
- typeof 연산자는 특정 값의 타입을 문자열로 반환하는 연산자다.
- 타입을 정의할 때 사용하면 특정 변수의 타입을 추론하는 기능을 갖는다!
type Person = typeof person;
// keyof 연산자는 뒤에 타입이 와야 한다.
function getPropertyKey(person: Person, key: keyof typeof person) {
return person[key];
}
const person = {
name: 'summermong',
age: 27,
};
getPropertyKey(person, 'name');
- 즉 변수 person의 타입을 추론해 Person 타입이 된다.
맵드 타입
- 기존 객체 타입을 기반으로 새로운 객체 타입을 만드는 타입 조작 기능
interface User {
id: number;
name: string;
age: number;
}
function fetchUser(): User {
return {
id: 1,
name: "summermong",
age: 27,
};
}
function updateUser(user: User) {
// 유저 정보 수정 기능이 있다고 칩시다
}
updateUser({ // ❌
name: 'wintermong';
});
- 유저 정보를 관리하는 프로그램을 만들기 위해 User 객체 타입을 정의한다.
- 유저 한 명의 정보를 불러 오는 함수 fetchUser를 정의했고, 유저 한 명의 정보를 수정하는 함수 updateUser를 정의 했다.
- 이 때 updateUser에는 수정한 내용만 전달하고 싶은데, updateUser의 매개변수 user의 타입이 User이기 때문에 수정하지 않은 프로퍼티들도 전달해야 하는 번거로움이 생긴다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
id?: number;
name?: string;
age?: number;
}
(...)
function updateUser(user: PartialUser) {
// ... 유저 정보 수정 기능
}
updateUser({ // ✅
name: 'wintermong'
});
- 이 때 User와 거의 유사하지만 선택적 프로퍼티만 담고 있는 PartialUser라는 타입을 새로 만들어줬다.
- 그리고 updateUser의 매개변수 user의 타입을 이것으로 바꿔주면 선택적 프로퍼티기 때문에 수정한 것만 전달해도 된다.
- 하지만 User와 PartialUser는 불필요하게 중복되는 코드가 많다.
- 이를 개선하기 위해 맵드 타입을 이용한다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in "id" | "name" | "age"]?: User[key];
};
(...)
- [key in 'id' | 'name' | 'age']는 해당 객체 타입의 key가 한번은 id, 한번은 name, 한번은 age가 된다는 뜻이다.
- 따라서 3개의 프로퍼티를 갖는 객체 타입으로 정의된다.
- key가 “id” 일 때 → id : User[id] → id : number
- key가 “name”일 때 → name : User[user] → name : string
- key가 “age”일 때 → age : User[age] → age : number
- [key in 'id' | 'name' | 'age']? 라고 하면 모든 프로퍼티가 선택적 프로퍼티가 된다.
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = {
[key in keyof User]?: User[key];
};
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
(...)
- 이 때 keyof 연산자를 쓰면 User 객체의 프로퍼티를 순회한다.
- 또 모든 프로퍼티가 읽기 전용 프로퍼티가 된 타입으로도 만들 수 있다!
템플릿 리터럴 타입
- string 리터럴 타입을 기반으로 특정 패턴을 갖는 문자열 타입을 만드는 기능
type Color = 'red' | 'black' | 'green';
type Animal = 'dog' | 'cat' | 'chicken';
type ColoredAnimal = `${Color}-${Animal}`;
const coloredAnimal: ColoredAnimal = 'black-cat'
- Color 타입과 Animal 타입은 각각 3개의 string literal 타입으로 이뤄진 union 타입이다.
- ColoredAnimal 타입은 이 둘을 조합해 만들 수 있는 모든 가지수의 string literal 타입으로 이루어진 union 타입이다.
- 만약 Color, Animal 타입의 값이 바뀌면 ColoredAnimal 타입에 추가해야 하는 것들이 점점 많아지고, 수정도 번거롭게 된다.
- 이 때 템플릿 리터럴 타입을 쓰면 알아서 조합해준다!
**출처: 한 입 크기로 잘라먹는 타입스크립트 (인프런, 이정환 강사님)
728x90
'❔ TypeScript > 💭 한 입 크기 TypeScript' 카테고리의 다른 글
[TypeScript] 유틸리티 타입 (0) | 2023.12.30 |
---|---|
[TypeScript] 조건부 타입 (1) | 2023.12.30 |
[TypeScript] 프로미스와 제네릭 (0) | 2023.12.22 |
[TypeScript] 제네릭 클래스 (0) | 2023.12.22 |
[TypeScript] 제네릭 인터페이스 (0) | 2023.12.21 |