1. 기본 타입 호환
/**
* unknown 타입
*/
function unknownExam() {
let a: unknown = 1;
let b: unknown = 'hello';
let c: unknown = true;
let d: unknown = null;
let e: unknown = null;
let unknownVar: unknown;
// unknown 타입은 모든 타입들의 부모이므로 다운캐스팅 불가
// let num: number = unknownVar;
// let str: string = unknownVar;
// let bool: boolean = unknownVar;
}
/**
* never 타입
*/
function neverExam() {
function neverFunc(): never {
while(true) {}
}
// never 타입 모든 타입의 자식이므로 업캐스팅이 가능함
let num: number = neverFunc();
let str: string = neverFunc();
let bool: boolean = neverFunc();
}
/**
* void 타입
*/
function voidExam() {
function voidFunc(): void {
console.log('log');
}
let voidVar: void = undefined;
}
/**
* any 타입 (계층도를 무시하는 치트키 타입)
*/
function anyExam() {
let unknownVar: unknown;
let anyVar: any;
let undefinedVar: undefined;
let neverVar: never;
anyVar = unknownVar;
undefinedVar = anyVar;
//neverVar = anyVar; -> 에러 발생
}
객체 관계도를 살펴보면 이해하기가 쉽다.
주로 다운캐스팅이 불가능하다고 생각하면 되며, any타입만을 예외로 생각하면 된다.
2. 객체 타입의 호환성
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: "진도",
};
animal = dog; // ✅ OK
// dog = animal; // ❌ NO
let parentDog: Animal = dog; // ✅ OK
위의 코드에서 Animal 타입과 Dog 타입에 같은 프로퍼티가 명시되었을 경우, 더 적은 공통 프로퍼티를 가진 타입이 부모타입이 된다.
위의 경우는 Animal이 Dog보다 프로퍼티가 적으므로 부모 프로퍼티가 되어 animal에 '업캐스팅' 할 수 있다.
3. 대수 타입
여러 개의 타입을 합성해서 새롭게 만들어낸 타입.
합집합 타입과 교집합 타입이 존재한다.
3.1. 합집합 타입 (Union 타입)
let a: string | number | boolean;
a = 1;
a = '문자열';
a = true;
기본 타입을 합집합 타입으로 만들 수 있다.
type Dog = {
name: string,
color: string,
};
type Person = {
name: string,
language: string,
}
type Union1 = Dog | Person;
또한 사용자가 새롭게 만든 타입들의 합집합 타입도 만들 수 있다.
let union1: Union1 = {
name: '',
color: '',
};
let union2: Union1 = {
name: '',
language: '',
};
let union3: Union1 = {
name: '',
color: '',
language: '',
}
// 아래 타입은 Dog나 Person의 합집합이 아니므로 에러
// let union4: Union1 = {
// name: '',
// }
Union1 은 Dog와 Person의 합집합 타입이므로 union4는 해당되지 않는다. (name만 존재하는 타입은 존재하지 않음)
3.2. 교집합 타입 (Intersection 타입)
let var1: number & string; //교집합이 없으므로 never 타입
마찬가지로 기본 타입도 교집합 타입이 가능하나, 둘의 교집합을 만족하는 타입은 기본적으로 없으므로 never 타입이 된다.
//Dog 와 Person의 교집합 타입이므로 전부 포함해야 한다.
type Intersection = Dog & Person;
let intersection1: Intersection = {
name: '',
color: '',
language: '',
}
4. 타입 추론
// number 타입으로 타입 추론
let a = 10;
// string 타입으로 추론
let b = "hello";
값을 대입한 경우 타입스크립트는 자동으로 해당 타입을 추론한다.
// id, name, profile, urls 프로퍼티가 있는 객체 타입으로 추론
let c = {
id: 1,
name: "junny",
profile: {
nickname: "nick",
},
urls: ["google.com"],
};
객체 또한 알아서 타입 추론 한다.
// 구조분해할당 타입 추론
let { id, name, profile } = c;
let [one, two, three] = [1, "hello", true];
구조 분해 할당도 타입 추론한다.
// 반환값이 string 타입으로 추론된다
function func() {
return "hello";
}
함수는 반환값 기준으로 타입을 추론하므로, func는 string 타입이다.
// 10 Number Literal 타입으로 추론
const num = 10;
// num = 20; //타입스크립트에서의 const는 리터럴 상수 타입이기 때문에 에러 발생
const 의 경우 자바스크립트와는 다르게 추가 대입이 불가능하다. (진정한 상수가 된다.)
5. 타입 단언
let num1 = 10 as never;
let num2 = 10 as unknown;
슈퍼타입이거나 서브타입인 경우에 타입 단언을 실행할 수 있다.
/**
* const 단언
*/
let num4 = 10 as const; //let을 const로 만드는 것과 같음
//각 프로퍼티에 readonly를 사용한 것과 같음
let cat = {
name: '야옹이',
color: 'yellow',
} as const;
const 단언에서 객체를 as const 하면 모든 프로퍼티에 readonly 키워드가 붙는다.
/**
* Non Null 단언
*/
type Post = {
title: string,
author?: string,
}
let post: Post = {
title: '게시글1',
author: '사용자1',
}
const len: number = post.author!.length;
Non Null 단언은 프로그래머가 임의로 해당 속성이 있다고 단언하는 것이다.
6. 타입 좁히기
/**
* 타입 좁히기
* 조건문 등을 이용해 넓은 타입에서 좁은 타입으로
* 타입을 상황에 따라 좁히거나 넓히는 것
*/
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.getDate());
} else if (value && 'age' in value) {
console.log(`${value.name} 은 ${value.age}살 입니다.`);
}
}
기본적인 타입은 typeof 를 사용하지만, Date와 같은 객체나 null의 구분을 위해선 instanceof 를 사용해야 한다.
그러나 사용자가 생성한 타입은 객체가 아니므로 instanceof를 사용할 수 없으므로 in 키워드를 사용한다.
value && 'age' 인 이유는 value가 있음을 보장하는 것이다.
value !== null && 'age' 와 같다고 보면 된다.
7. 서로소 유니온 타입 (Tagged 유니온 타입)
enum Tag {
ADMIN,
MEMBER,
GUEST,
}
type Admin = {
tag: Tag.ADMIN,
name: string,
kickCount: number,
};
type Member = {
tag: Tag.MEMBER,
name: string,
point: number,
};
type Guest = {
tag: Tag.GUEST,
name: string,
visitCount: number,
};
type User = Admin | Member | Guest;
Admin, Member, Guest 는 tag 를 기준으로 연결되어 있다.
// Admin -> {name}님 현재까지 {kickCount}명 강퇴했습니다.
// Member -> {name}님 현재까지 {point} 모았습니다.
// Guest -> {name}님 현재까지 {visitCount}번 오셨습니다.
function login(user: User) {
switch (user.tag) {
case Tag.ADMIN:
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
break;
case Tag.MEMBER:
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
break;
case Tag.GUEST:
console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다.`);
break;
}
}
User는 Admin, Memeber, Guest 중 하나이면서 tag를 포함하고 있으므로, tag를 기준으로 나눌 수 있다.
type LoadingTask = {
state: 'LOADING',
}
type FailedTask = {
state: 'FAILED',
error: {
message: string,
},
}
type SuccessTask = {
state: 'SUCCESS',
response: {
data: string,
},
}
type AsyncTask = LoadingTask | FailedTask | SuccessTask;
만약 위와 같은 서로소 유니온 타입을 만들었다면
function processResult(task: AsyncTask) {
switch (task.state) {
case 'LOADING':
console.log('로딩 중');
break;
case 'FAILED':
console.log(`에러 발생 : ${task.error.message}`);
break;
case 'SUCCESS':
console.log(`성공 : ${task.response.data}`);
break;
}
}
각 케이스에서 이미 해당 타입(LoadingTask 또는 FailedTask 또는 SuccessTask)임을 나타내므로 error 나 response를 쓸 수 있게 된다. (옵셔널 체이닝이나 Non null을 하지 않아도 된다.)
'프로그래밍 > 타입스크립트' 카테고리의 다른 글
타입스크립트 - 함수 타입 표현식과 호출 시그니처 (0) | 2025.02.14 |
---|---|
타입스크립트 - 함수 타입 (0) | 2025.02.14 |
타입스크립트 기본타입 (1) | 2025.02.13 |
타입스크립트 설정 - tsconfig.json (0) | 2025.02.13 |
타입스크립트 설치 및 설정 (0) | 2025.02.13 |