타입스크립트를 간단하게만 사용하더라도 코드 작성에서 꽤 많은 이점을 가져올 수 있다. 하지만 타입스크립트에는 다른 정적 타이핑을 가지는 프로그래밍 언어보다 복잡한 타이핑이 가능하도록 되어있다. 예를 들면 lookup type
, mapped type
, keyof
등이 있는데, 대충 보면 무슨 소리인지 하나도 모르겠지만 알고 나면 코드 길이를 꽤 많이 줄일 수 있다.
하지만 사용 방법이 생각보다 복잡하고 나 역시 뭔지는 정확히 모른채로 대충 이런식으로 동작하는 것을 알아서 사용해 왔던 타입스크립트의 중급 문법을 정확히 알기 위해 다뤄보고자 한다. 이 내용에 대해서 시리즈를 여러 개 작성하는 것은 의미가 없어 보이므로 이 글에 조금씩 쓰고 이 후에 배우게 되는 내용은 더 추가하는 식으로 작성해보도록 하겠다.
다른 프로그래밍 언어의 타이핑과 중첩되는 부분은 최대한 없애고
제가 몰랐던 부분과타입스크립트만의 특징을 가지는 중급 문법에 대해서만 다루겠습니다.
keyof
는 어떤 타입(이하 타입스크립트의 interface
와 혼용할 수 있습니다.) 의 키 부분들만을 모아 놓은 것을 뜻한다. 예를 들면
interface Person {
name: string;
age: number;
job: string;
}
Person
이라는 타입이 있다고 하자. 3개의 키 name
, age
, job
을 갖게 된다. 만약 우리가 이 사람의 정보를 보는 코드를 작성한다고 하자.
function watchPersonInfo(person: Person, key: string){
console.log(person[key]);
}
const pepper: Person = { name: "pepper", age:26.9, job: "developer" }
watchPersonInfo(pepper, 'name');\
/// output : pepper
위와 같이 작성했다. 전혀 문제 없이 name
키의 값인 pepper가 정상적으로 출력된다. 하지만 여기서
watchPersonInfo(pepper, 'address');
이렇게 없는 정보를 보여주려고 한다면 어떻게 될까? watchPersonInfo
함수의 파라미터인 key
가 string
으로 타이핑이 되어있기 때문에 그저 string
이면 타입스크립트가 오류를 발견해내지 못한다. 우리는 key
파라미터에 들어갈 인자가 Person
의 키이길 바란다. 영어로는 keyof Person
이 될것만 같다.
function watchPersonInfo(person: Person, key: keyof Person){
console.log(person[key]);
}
const pepper: Person = { name: "pepper", age:26.9, job: "developer" }
watchPersonInfo(pepper, 'address'); // 'address'에 error
그렇게 key
의 타입을 keyof Person
으로 바꿔주었더니 바로 'address'에 오류가 생긴다. 여기서 keyof Person
은 즉
'name' | 'age' | 'job'
과 같은 유니온 타입의 역할을 하게 되는 것이다.
위에서 사용한 pepper라는 사람의 정보를 읽을 수만 있고 바꿀 수는 없도록 코드를 작성한다고 하자. 즉
pepper.age = 35; // 여기서 에러를 발생시키고 싶다고 하자.
이런식으로 변경이 불가능하도록 해야한다. 이런 경우엔 readonly
라는 방법을 사용하면 된다. 즉 Person
을
interface Person {
readonly name: string;
readonly age: number;
readonly job: string;
}
이렇게 바꿔주면 우리가 원하던 대로 에러가 난다. 하지만 타입에 키가 수 없이 많다면 그 만큼 readonly
를 직접 타이핑해주어야 한다. 그리고 어느날 우리는 readonly
가 있는 타입과 없는 타입이 모두 필요하다고 할 때 두 개의 타입을 전부 직접 타이핑에서 만들어주게 될 것이다. 물론 복사붙여넣기를 할테지만 그만큼 코드의 길이가 길어진다.
이런 경우에 Mapped Types
라는 특징을 이용할 수 있는데, Mapped Types
는 아래와 같은 형태로 이루어진다.
type ReadonlyPerson = {
[loop]: output;
}
type
키워드를 사용해주어야 한다. 그렇지 않으면Array
타입을 갖는 요소가 된다.
여기서 이제 loop
와 output
을 채워보자면
type ReadonlyPerson = {
[K in 'name' | 'age' | 'job']: Person[K];
}
이렇게 된다. 이러면 Person
내에 모든 name
, age
, job
요소에 대한 키-밸류 타입 쌍들에 전부 적용시킬 수 있는 Mapped Types
과 완성된다. 위에서 배운 keyof
를 활용해서 조금 더 간단하게 해보자면
type ReadonlyPerson = {
[K in keyof Person]: Person[K];
}
그리고 모든 요소에 readonly
를 주고 싶으므로
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
}
이렇게 앞에 readonly를 붙여주기만 하면 된다. 여기서 더 간단하고 제네릭하게 가자면
type Readonly<T> = {
readonly [K in keyof T]: T[K];
}
이렇게 Person
까지 T
로 대체할 수 있다. 그리고 심지어 더 간단하게 가자면
const pepper: Readonly<Person> = { name: "pepper", age:26.9, job: "developer" };
지금까지 작성한 타입을 지워버리고 그냥 타입스크립트에 기본적으로 내장되어 있는 Readonly
를 쓰면 된다. 사실 Readonly
는 타입스크립트에 내장된 Utility Types
중 하나이지만 가장 대표적인 Mapped Types
를 사용하는 방법이기에 직접 구현해 보았다.
...