[Indexed Access Type]
- Indexed를 이용해서 다른 타입의 특정 Property 값을 추출하는 방식으로 객체,배열,튜플에서 사용이 가능하다. 특정 변수의 타입을 정의할 때 기존에 정의 되어있던 객체,배열,튜플의 특정 타입만 별도로 사용하고 싶을 때 유용하게 사용된다.
Object
이해하기 쉽도록 객체부터 코드로 예시를 들어보겠다.
//Object
interface User {
name: string,
age: number,
info: {
id: number,
gender: string
}
}
// 일반적인 방법
function printInfo(info: {id: number, gender: string}){
console.log(`${info.id}, ${info.gender}`);
}
//Indexex Access Type
function printInfo(info:User["info"]){
console.log(`${info.id}, ${info.gender}`);
}
const user: User = {
name: "Tom",
age: 10,
info: {
id:1,
gender: "Man"
}
}
Interface를 통해 객체 타입으로 정의된 User 타입이 존재한다. 해당 타입 정의에 맞춰 user라는 변수도 선언이 되어있다. 또한 User의 info 속성의 타입만 별도로 사용하는 함수 printInfo가 존재한다.
이 때 일반적인 방법으로 info 속성의 타입을 사용하는 경우에 해당 타입에 속성이 추가가 되는 경우는 info 타입을 사용하는 여러가지 함수에게 일일히 매개변수의 타입을 추가해주어야 하는 굉장히 번거롭고 비효율적인 상황이 발생한다. 이런 경우에 Indexed Access Type을 활용하여 특정 속성의 타입을 정의하여 사용하면 자동적으로 해당 속성의 타입을 모두 가져오기 때문에 효율적인 코드를 작성할 수 있게 된다.
여기서 주의해야 할 점이 있다. Indexed Access Type을 사용하는 경우 정의된 타입명 뒤에 대괄호를 사용하는데 대괄호 내에 들어가는 요소는 변수나 일반 문자열이 아닌 타입이 들어가는 것이다. 쌍따옴표(" ")로 인해 문자열로 오해를 하는 경우가 있는데 정확히는 타입이 들어가는 것이기 때문에 해당 타입명과 동일한 변수를 선언하는 경우는 오류가 발생할 수 있으니 주의해야 한다.
또한 위 예시 코드에서 보이듯이 info 속성을 사용하고 있는데 info 속성 타입 내부의 id 속성의 타입만 사용하고 싶은 경우는 배열의 형식에 맞추어 중첩 배열을 사용하면 된다. 이것 또한 코드로 간단히 살펴보자.
//Object
interface User {
name: string,
age: number,
info: {
id: number,
gender: string
}
}
const user: User = {
name: "Tom",
age: 10,
info: {
id:1,
gender: "Man"
}
}
function printInfo(info:User["info"]["id"]){
console.log(`${info.id}, ${info.gender}`);
}
printInfo(10.23); // 10
Array
다음으로는 배열을 사용한 경우를 알아보자.
배열의 경우 기존에 정의되어 있는 타입을 사용하여 생성하고자하는 변수의 타입을 정의하면서 타입 정의 뒤에 대괄호를 이용하여 배열의 index를 가져오듯이 number이나 숫자 값을 넣어주면 된다. 배열 내부에는 동일한 타입의 요소들이 반복이 될 것이므로 아무 index의 타입만 갖고 와서 사용하면 된다. 아래 코드를 보며 이해해보자.
//Array
type A = {
name: string,
age: number,
info: {
id: number,
type: string
}
}[];
const a: A[number] = {
name: "a",
age: 10,
info: {
id: 1,
type: "array"
}
}
function printA(info:A[number]["info"]){
console.log(`${info.id}, ${info.type}`)
}
A로 정의되어 있는 타입이 존재하며, 변수 a에 타입 A를 정의해주면서 선언을 해주었다. 이 때 타입 A를 정의해주면서 뒤에 대괄호를 통해 number을 넣어주며 특정 index의 타입을 동일하게 사용하겠다는 표현을 해주었다. 따라서 A로 정의된 객체 배열의 요소인 하나의 객체 타입과 동일한 속성과 타입을 이용해서 변수 선언이 가능해졌다. 함수에서도 동일하게 사용되는데 매개변수를 자세히 보면 A["info"]라는 타입은 객체에서 사용하는 Index Access Type 정의 방식이다. 하지만 A 자체는 객체를 요소로 갖는 배열 타입이므로 해당 배열에서 하나의 요소만 가져와 타입으로 사용해야하기 때문에 A 바로 뒤에 [number]을 포함하여 하나의 요소만을 갖고와 객체 타입을 사용할 수 있도록 한 것이다.
Tuple
Tuple Type은 상대적으로 더 간단하게 사용할 수 있다.
//Tuple
type tup = [number,string,boolean];
type tup1 = tup[0]; //number
type tup2 = tup[1]; //string
type tup3 = tup[2]; //boolean
type tup4 = tup[number]; //number|string|boolean
위의 코드와 같이 특정 index의 타입을 갖고와 별도로 선언하여 Tuple로 정의되어 있는 특정 타입을 사용할 수 있는 것이다. 마지막 줄의 tup4와 같은 경우는 앞서 살펴본 배열의 Indexed Access Type 정의 방식과 동일한 방식인데 본 방식은 Tuple로 정의되어 있는 타입을 살펴본 후 최적화하여 사용 가능한 타입들을 Union 타입으로 정의해주는 방식이다.
[Operator keyof]
Operator keyof
- 객체 타입에 사용하는 연산자이다. 객체 형태로 정의된 타입의 key 값들을 불러와 사용할 수 있는 연산자이다. 간단한 코드를 보며 살펴보자.
interface User {
name: string,
age: number
}
const user:User = {
name: "tom",
age: 10
}
function getUser(user:User,key: keyof User){
return user[key]
}
getUser(user,"name"); //Tom
getUser(user,"age"); //10
위의 코드 예시를 보면 getUser 함수는 user 변수의 특정 key에 해당하는 value 값을 반환하는 함수이다. 이 때 함수의 매개변수로 key를 줄 때 key의 타입을 String Literal Type으로 User 객체 타입이 갖고 있는 key 값을 일일히 작성해 Union 타입으로 설정하는 것도 가능은 하지만 만약 key의 개수가 매우 많은 경우에는 코드의 가독성과 효율성이 떨어지게 된다. 따라서 keyof 연산자를 통해서 key 값을 편리하게 가져올 수 있는 방식이다.
이 때 주의해야할 점은 keyof 뒤에는 반드시 정의된 타입이 와야한다. 그렇지 않으면 연산 오류가 발생하기 때문에 주의해야한다.
Operator typeof
keyof 연산자와 비슷한 형태의 연산자가 존재한다. 바로 typeof 연산자이다. typeof 연산자는 Type Alias에서 타입 추론으로 사용할 수 있는 방식으로 특정 변수를 생성할 때 변수의 타입을 바로 정의해주는 것이 아닌 정의된 변수로 Type Alias의 타입을 추론하여 타입 정의를 역으로 생성하는 기능을 제공한다. 코드로 보면 이해가 쉬울 것이다.
//typeof
type User = typeof user;
const user = {
name: "tom",
age: 10
}
function getUser(user: User, key: keyof typeof User){ //typeof user == User
return user[key];
}
getUser(user,"age"); // 10
따로 타입이 정의되지 않은 변수 user에 의해 타입 User의 타입이 자동적으로 추론되며 자연스럽게 keyof 연산자와 함께 사용이 가능하기도 하다.
[Mapped Type]
Mapped Type
- 객체 타입을 조작하는 타입이다. 실무에서 굉장히 많이 사용되는 문법중에 하나이고 객체 타입을 조작하는 방법인데도 불구하고 Interface로는 정의가 불가능하여 Type Alias로만 정의를 해야하는 문법이다. 간단한 예시로 객체 타입으로 정의된 Interface 타입과 그 타입을 사용하여 유저를 생성하는 함수와 유저 정보를 수정하는 함수가 존재한다는 코드를 만들어보자. 이 때 Mapped Type이 사용되는 경우는 유저 정보를 수정하는 함수에서 모든 속성을 불러오거나 수정할 필요 없이 원하는 속성만 다룰 수 있도록 하는 경우이다. 아래 코드를 보자.
interface User {
id: number,
name: string,
age: number
}
type newUser = {
[key in "id"|"name"|"age"] : User[key] //General Mapped Type
}
type newUser1 = {
[key in "id"|"name"|"age"]? : User[key] //Optional Property
}
type newUser2 = {
[key in keyof User] : User[key] //Use keyof operator
}
type newUser3 = {
[key in keyof User] : boolean //Change type
}
type newUser4 = {
readonly [key in keyof User] : User[key] //Add a readonly keyword
}
function fetchUser():User{
// ...get logic
return {
id: 1,
name:"tom",
age:10
}
}
function updateUser(user:newUser1){
// ...update logic
}
updateUser({
age: 10
})
하나씩 코드를 살펴보자.
Interface로 정의된 User 객체 타입이 있다. 그리고 유저 정보를 가져오는 함수 fetchUser, 유저 정보를 수정하는 함수 updateUser가 있다. fetchUser 함수는 User 타입에 맞게 데이터를 반환하고 updateUser 함수는 매개변수로 user를 받는다. 이 때 매개변수 user의 타입이 될 수 있는 여러가지 타입을 Mapped Type으로 정의해두었다.
- newUser은 일반적인 Mapped Type 문법을 사용하여 선언한 타입이다. key in 키워드와 Union 타입으로 String Literal 값들을 User의 Indexed Access Type으로 정의한 것이다.
- newUser1은 newUser로 정의된 타입들을 Optional Property로 적용한 문법이다. newUser 타입은 모든 속성이 필수적으로 사용되어야 하기 때문에 유저 정보 수정 시 불필요하게 입력되어야 하는 속성들이 존재하게 되기 때문이다. Optional Property를 사용한다면 해당 속성들을 사용해도 되고 사용하지 않아도 되게 되므로 updateUser 함수의 목적과 상응한다.
- newUser2은 String Literal과 Union 타입으로 정의된 key 값들을 keyof 연산자를 통해 코드 최적화를 한 방법이다.
- newUser3은 keyof 연산자를 통해 접근한 key 값들의 타입을 모두 변경해주는 방법이다. value의 타입에 해당하는 부분에 변경하고자 하는 타입을 넣어주면 된다.
- newUser4는 모든 key 즉 객체의 속성들을 readonly(읽기 전용) 속성으로 변경하는 방법이다. 객체의 속성값들을 안전하게 보호하는데에 사용된다.
[Template Literal Type]
Template Literal Type
- 우리가 쉽게 접할 수 있는 Literal Type으로 정의된 타입을 조합하는 문법이다. Union 타입으로 정의된 타입들을 각각 조합하고 싶을 때 사용하는데 많이 사용되거나 접하게 되는 일이 적기 때문에 간단하게 코드로만 살펴보고 끝내보자.
type A = 'a' | 'b' | 'c';
type B = 'c' | 'd' | 'e';
type AB = `${A}-${B}`
type C = 1 | 2 | 3;
type D = 4 | 5 | 6;
type CD = `${C}-${D}`
'Typescript' 카테고리의 다른 글
[TypeScript] - (10) Utility Type (1) | 2024.09.04 |
---|---|
[TypeScript] - (9) 조건부 타입 (1) | 2024.08.22 |
[TypeScript] - (7) Generic(제네릭) (2) | 2024.08.07 |
[TypeScript] - (6) Class (0) | 2024.08.05 |
[Typescript] - (5) Interface (0) | 2024.08.01 |