TS 정리 (한 입 크기로 잘라먹는 타입스크립트(TypeScript)) - 03
해당 포스팅은 한 입 크기로 잘라먹는 타입스크립트(TypeScript)를 학습하면서 알게된 정보들을 정리하였습니다.
한 입 크기로 잘라먹는 타입스크립트(TypeScript) 강의 | 이정환 Winterlood - 인프런
이정환 Winterlood | , 프론트엔드의 피할 수 없는 대세 타입스크립트,이제는 제대로 정복할 때가 왔습니다! 😎 [사진]인프콘 2023 '타입스크립트는 왜 그럴까?' 발표자의 강의입니다. 🧐 배워
www.inflearn.com
기본 타입과 리터럴 타입
let num1: number = 10;
let num2: 10 = 10;
num1 = num2;
num2는 10의 값만 가지는 리터럴 타입, num1은 10의 값을 가진 number타입인 경우, 리터럴 타입의 값을 number에 주입할 수 있습니다.
단, num2에 num1을 주입할 수 없습니다.
왜냐하면, num1의 경우 언제든지 10이 아닌 값을 가질 수 있으므로, 10의 값만 허용하는 리터럴 타입의 경우 문제가 발생하게 됩니다.
객체 타입간의 호환성
type Animal = {
name: string;
color: string;
}
type Dog = {
name: string;
color: string;
breed: string;
}
let animal: Animal = {
name: "기린",
color: "yellow",
}
let dog: Dog = {
name: "멍멍이",
color: "brown",
breed: "진도"
}
다음과 같이 타입이 존재하고, 해당 type에 대한 변수가 존재할때
animal = dog;
animal에 dog는 주입이 가능하지만, dog에 animal은 주입이 불가능합니다.
왜냐하면, dog에는 animal에 없는 breed key값이 존재하기 때문입니다.
합집합 타입
합집합 타입은 여러 개의 타입을 합성해서 하나의 타입으로 만드는 타입입니다.
let a: string | number | boolean;
다음과 같이 선언한 경우, a는 string 타입, number타입, boolean 타입의 값들을 모두 사용가능한 타입이 됩니다.
let a: string | number | boolean;
a = 1;
a = "hello";
a = true;
교집합 타입
교집합 타입은 합집합 타입과는 다르게 여러 개의 타입의 공통점을 가지는 타입만 선언이 가능하게 됩니다.
let a: number & string;
다음과 같이 선언하는 경우, a는 number타입과 string 타입의 공통점인 타입을 가지게 됩니다.
number하고 string타입의 교집합은 존재하지 않으므로 변수 a는 never타입이 됩니다.
교집합 타입의 경우 type 변수 선언시에 주로 사용됩니다.
type Dog = {
name: string;
color: string;
}
type Person = {
name: string;
language: string;
}
type Intersection = Dog & Person;
let intersection: Intersection = {
name: "",
color: "",
language: "",
}
Dog 타입은 name, color를 Person타입은 name, language를 가지고 있을 때, Dog와 Person의 교집합 타입은 name, color, language를 모두 가지고 있어야만 합니다.
타입 추론
타입 추론의 경우 ts에서 타입을 선언하지 않은 경우 추론해서, 처음에 지정되는 값을 통해 해당 변수가 무슨 타입을 가지고 있는지 추론해주는 역할을 합니다.
let a = 10;
a = true; // 에러
변수 a에 number이라는 타입을 주어지지 않았지만, ts에서 a의 값이 10이므로, a의 타입은 number이라고 지정해줍니다.
'ts는 js와 다르게 타입이 존재한다는데, 이러면 ts를 쓰는 의미가 없어지지 않나요?' 라고 생각할 수도 있지만, ts는 타입 추론을 통해 해당 타입에만 사용가능한 함수만을 사용할 수 있도록 해줍니다.
물론, 타입을 지정해주는게 다른 사람이 코드를 볼 때, 해당 변수의 타입을 확실하게 이해할 수 있어서 좋은 면도 있지만, 타입을 지정하지 않아도 이미 초기값이 선언되어져 있어 코드를 이해하는데는 문제가 없을 것으로 보입니다.
타입 단언
타입 단언은 as를 통하여 선언이 가능하고, 해당 값의 타입이 as로 선언된 타입이라고 TypeScirpt가 믿게 만듭니다.
type Person = {
name: string,
age: number,
};
let person = {};
person.name = "lms0806"; // 오류
person.age = 29; // 오류
다음과 같이 선언되어져 있는 경우, person의 타입은 지정되어져 있지 않아, person 변수의 값을 수정하는데 오류가 발생합니다.
let person: Person = {} // 오류
다음과 같이 선언하는 경우, person은 Person 타입에 대한 기본 값(name, age)가 선언되어져 있지 않기 때문에 오류가 발생하게 됩니다.
let person = {} as Person;
다음과 같인 선언하는 경우, person에는 초기값이 선언되어져 있지 않지만, Person타입으로 TypeScript에게 이야기를 해, 추후에 값을 수정하는데 문제가 발생하지 않습니다.
타입 단언의 규칙
타입 단언을 사용하기 위해서는 A가 B의 슈퍼 타입이거나, 서브타입이어야 합니다.
let num1 = 10 as never;
let num2 = 10 as unknown;
unknown은 모든 타입들의 super 타입이고, never는 모든 타입들의 sub 타입이여서 다음과 같이 타입 단언이 가능합니다.
let num3 = 10 as string; // 오류
다음과 같이 사용하는 경우, num3는 number타입을 가지고 있고, as로 타입단언된 타입은 string으로 서로 겹치는 부분이 존재하지 않는 타입이라 오류가 발생하게 됩니다.
※주의 사용할 경우 매우 조심해야 함
let num4 = 10 as unknown as string;
다음과 같이 사용하는 경우, 10의 초기 타입은 number, number을 unknwon으로 타입단언이 가능하니 num4의 타입은 unknwon이 됩니다. 이후, unknown 타입과 string 타입과는 타입 단언이 가능하여 10의 값을 가진 string 타입이 됩니다.
Non Null 단언
값을 선언하다보면, 해당 값이 null이나 undefined 값을 가지게 되는 경우가 발생합니다.
type Post = {
title: string,
author?: string,
};
let post: Post = {
title: "게시글1",
author: "작성자1"
}
author는 작성자가 없을 수도 있으므로 ?를 선언하여 null이 가능하도록 선언합니다.
이런 경우, author에 string 함수를 적용하는 경우, author값이 null인 케이스도 존재하여 TypeScript는 에러를 출력합니다.
const len: number = post.author!.length;
다음과 같이 ! 연산을 사용하게 되면, post.author의 값은 null이나 undefined가 아니라고 TypeScript에 이야기를 해, length 함수를 정상적으로 사용할 수 있게 됩니다.
타입 좁히기
타입 좁히기는 조건문 등을 사용하여 다중 타입을 하나의 타입으로 좁히는 방법을 의미합니다.
function func(value: number | string) {
if (typeof value == "number") {
console.log(value.toFixed());
}
else if (typeof value == "string") {
console.log(value.toUpperCase());
}
}
다음 함수에서는 value가 number타입이나 string타입이 될 수 있는 환경에서, value가 number타입이면 toFixed(), value가 string 타입이면 toUpperCase()를 사용하도록 if 조건문을 통하여 제어한 모습니다.
function func(value: number | string | Date | null) {
if (typeof value == "number") {
console.log(value.toFixed());
}
else if (typeof value == "string") {
console.log(value.toUpperCase());
}
else if (typeof value == "Object") {
console.log(value.getTime());
}
}
만약 number | string | Date | null과 같은 타입을 가진 변수가 인자로 주어지는 경우에 Date 타입인 경우 getTime 함수를 호출하도록 만들고 싶은 경우, 다음과 같이 사용이 가능합니다.
그러나 null 또한 Object 객체여서 다음과 같이 사용하는 경우 value가 null일 수 있어 오류가 발생합니다.
function func(value: number | string | Date | null) {
if (typeof value == "number") {
console.log(value.toFixed());
}
else if (typeof value == "string") {
console.log(value.toUpperCase());
}
else if (value instanceof Date) {
console.log(value.getTime());
}
}
그러므로 instanceof 라는 방식을 사용하여 해당 타입을 비교할 수 있습니다.
추가로, TypeScript에서 주어지는 type이 아닌 객체 type을 선언하여 해당 타입도 포함하는 경우에는 어떻게 해야할까요?
type Person = {
name: string,
age: number,
};
function func(value: number | string | Date | null | Person) {
if (typeof value == "number") {
console.log(value.toFixed());
}
else if (typeof value == "string") {
console.log(value.toUpperCase());
}
else if (value instanceof Date) {
console.log(value.getTime());
}
else if (value && "age" in value) {
console.log(`${value.name}은 ${value.age}살 입니다.`)
}
}
number, string, Date 타입을 모두 조건문으로 처리하고 나면, null타입과 Person 타입만 남게 됩니다.
Person 타입의 경우 name하고 age를 값으로 가지고 있기 때문에, value가 "age"나 "name"을 가지고 있는 경우 Person 타입이라는 것을 확인할 수 있게 됩니다.
그러나, 아직 null타입을 제외시켜주지 않았기 때문에, 마지막 if문에서 value가 null일 수 있어 value && 와 같이 value값이 null이 아닌 경우에 대한 조건도 추가하였습니다.
서로소 유니온 타입
서로소 유니온 타입의 경우, 교집합이 없는 타입들로만 이루어진 유니온 타입을 말합니다.
type Admin = {
name: string,
kickCount: number,
};
type Member = {
name: string,
point: number,
};
type Guest = {
name: string,
visitCount: number,
};
다음과 같이 이루어질 때, name, kickCount, point, visitCount
가 존재하는 경우, 모든 타입을 만족하게 됩니다.
type User = Admin | Member | Guest;
다음과 같이 선언하는 경우, 입력받은 값이 Admin
인지, Member
인지, Guest
인지 확인하기 위해서는 해당 타입의 변수명인 kickCount
, point
, visitCount
중 어떤 것을 가지고 있는지를 체크하는 다음과 같은 방식이 존재합니다.
let user:User;
if ("kickCount" in user) {
}
else if ("point" in user) {
}
else {
}
그러나, 단순 조건문만을 보고 이를 확인하기에는 어려움이 있습니다.
이를 해결하기 위한 방법으로, 각 type들에 같은 이름을 가진 변수를 선언하고, 각각의 값을 주어지는 경우, 해당 변수의 값만을 확인하여 어떤 타입인지 유추가 가능합니다.
type Admin = {
tag: "ADMIN",
name: string,
kickCount: number,
};
type Member = {
tag: "MEMBER",
name: string,
point: number,
};
type Guest = {
tag: "GUEST",
name: string,
visitCount: number,
};
if (user.tag == "ADMIN") {
}
else if (user.tag == "MEMBER") {
}
else {
}
여기서 추가로, if문을 다음과 같이 보기 쉬운 switch 문으로도 변환이 가능합니다.
switch (user.tag) {
case "ADMIN": {
break;
}
case "MEMBER": {
break;
}
case "GUEST": {
break;
}
}