2025년 3월 3일
타입 단언은 실제로 값을 그 타입으로 바꾸는 것은 아님(업캐스팅/ 다운캐스팅이 아님)
빈 객체로 초기화 후, '.연산자'를 사용해서 프로퍼티를 추가하면 오류 발생
타입 정의를 해서 문제가 되는 것 같아 타입을 지정하지 않아도 빈 객체를 기준 값으로 추론되기 때문에 그냥 빈 객체가 변수의 타입이 됨
/**
* 타입 단언
*/
type Person = {
name: string;
age: number;
};
let person = {};
person.name = '이정환'; // 오류 발생
person.age = 27; // 오류 발생
그럼 어떻게 해야할까?
값의 타입을 프로그래머가 직접 단언 하는 방법 = 타입 단언(type assertion)
/**
* 타입 단언
*/
type Person = {
name: string;
age: number;
};
let person = {} as Person;
person.name = '이정환';
person.age = 27;
다른 경우를 살펴보자.
변수를 초기화할 때, 객체 리터럴로 초기값을 설정하면 초과 프로퍼티 검사가 발동됨 -> 추가 프로퍼티를 허용하지 않음 -> 그런데 어쩔 수 없이 추가 프로퍼티를 꼭 넣어야한다면?
type Dog = {
name: string;
color: string;
};
let dog: Dog = {
name: '돌돌이',
color: 'brown',
breed: '진도' // 오류 발생
}
type Dog = {
name: string;
color: string;
};
let dog = {
name: '돌돌이',
color: 'brown',
breed: '진도'
} as Dog;
타입 단언은 아무 상황에서나 쓰는 문법은 아님
타입 단언의 규칙을 지켜야함
단언식 : 값 as 단언 (A as B)
let num1 = 10 as never;
// 10은 never의 슈퍼타입
let num2 = 10 as unknown;
// 10은 unknwon의 서브타입
// let num3 = 10 as string 오류 발생
let num3 = 10 as unknown as string;
// 다중 선언 하면 되지만 권장x
const 키워드를 써서 변수를 선언하는 것처럼 만들어줌
객체 타입을 사용할 때 활용도가 높음
let num4 = 10 as const;
let cat = {
name: '야옹이',
color: 'yellow'
} as const;
// cat.name=''; 오류
어떤 값이 null이거나 undefined이 아니라고 타입스크립트 컴파일러에게 알려주는 역할을 함
예를 들어서, 게시판을 만들때 게시글 객체 타입 정의를 할 때 익명으로 게시글을 남길 수도 있어서 타입에 선택적 프로퍼티를 추가 -> 변수를 만들어 초기화하고 게시글을 하나 만듦 -> author 이름의 길이를 몇 개인지 출력하는 기능을 만들어 보려 함
length 프로퍼티를 사용하면 자동으로 'post.author?.length'; 하고 ?(옵셔널 체이닝)가 추가되는 것을 볼 수 있음
그런데 값 전체가 undefined이 될 수 있어서, number 타입으로 정의한 len은 undefined가 될 수 없으므로 오류가 발생함
이럴 때 'non null' 단언 사용 -> ! (author라는 프로퍼티는 있어! 하고 강조하는 느낌)
non null 단언 : 이 값이 null 이거나 undefined이 아닐 것이라고 타입스크립트 컴파일러가 믿도록 만듦
type Post = {
title: string;
author?: string;
};
let post: Post = {
title: '게시글1',
};
// const len: number = post.author?.length; 값 자체가 undefined가 될 수 있음 -> 오류
const len: number = post.author!.length;
조건문 등을 이용해 넓은 타입에서 좁은 타입으로 타입을 상황에 따라 좁히는 방법을 말함
타입가드: 조건문과 함께 타입을 좁힐 수 있는 표현들
// value => number: toFixed
// value => string: toUpperCase
function func(value: number | string) {
value; // string | number 타입으로 추론
// value.toUpperCase(); 오류
// value.toFixed(); 오류
if (typeof value === 'number') {
console.log(value.toFixed()); // number 타입으로 추론
} else if (typeof value === 'string') {
console.log(value.toUpperCase()); // string 타입으로 추론
}
}
function func(value: number | string | Date | null) {
if (typeof value === 'number') {
console.log(value.toFixed()); // number 타입으로 추론
} else if (typeof value === 'string') {
console.log(value.toUpperCase()); // string 타입으로 추론
} else if (typeof value == 'object') {
console.log(value.getTime()); // 오류 발생
// value는 Date | null 값이 될 수 있는데 null은 getTime 메서드를 적용할 수 없음
}
}
이럴 때는 instanceof 연산자 사용
그런데, 타입 별칭을 사용해 타입을 정의한 후, instanceof를 연산자를 적용하면 오류가 발생함
function func(value: number | string | Date | null | Person) {
if (typeof value === 'number') {
console.log(value.toFixed()); // number 타입으로 추론
} else if (typeof value === 'string') {
console.log(value.toUpperCase()); // string 타입으로 추론
} else if (value instanceof Date) {
console.log(value.getTime()); // Date 타입으로 추론
} else if (value instanceof Person) {
console.log(`${value.nmae}은 ${value.age}살 입니다.`); // 오류 발생
}
}
type Person = {
name: string;
age: number;
}
// value => number: toFixed
// value => string: toUpperCase
// value => Date: getTime
// value => Person: name은 age살 입니다.
function func(value: number | string | Date | null | Person) {
if (typeof value === 'number') {
console.log(value.toFixed()); // number 타입으로 추론
} else if (typeof value === 'string') {
console.log(value.toUpperCase()); // string 타입으로 추론
} else if (value instanceof Date) {
console.log(value.getTime()); // Date 타입으로 추론
} else if (value && 'age' in value) {
console.log(`${value.nmae}은 ${value.age}살 입니다.`); // Person 타입으로 추론
}
}
타입 좁히기를 할 때 더욱 직관적이게 타입을 좁힐 수 있도록 객체 타입을 정의하는 방법
서로소 유니온 타입
수학에서는 교집합이 하나도 없는, 즉 공통 원소가 하나도 없는 집합을 서로소 관계에 있다고함
언제 사용하면 좋을까?
type Admin = {
name: string;
kickCount: number;
};
type Member = {
name: string;
point: number;
};
type Guest = {
name: string;
visitCount: number;
};
type User = Admin | Member | Guest;
// Admin -> {name}님 현재까지 {kickCount}명 강퇴했습니다.
// Member -> {name}님 현재까지 {point} 모았습니다.
// Guest -> {name}님 현재까지 {visitCount}번 오셨습니다.
function login(user: User) {
if('kickCount' in user) {
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
} else if ('point' in user) {
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
} else {
console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다.`);
}
}
함수만 보고 admin, member, guest를 한눈에 파악하기 어려움. 결국 위의 타입 프로퍼티들을 봐야 알 수 있음
이럴 때 사용하는게 서로소 유니온 타입!
아래처럼 string literal 타입을 사용해서 admin, member, guest의 교집합이 없게 만듦 -> 서로소 집합의 관계를 갖게 됨
type Admin = {
tag: 'ADMIN',
name: string;
kickCount: number;
};
type Member = {
tag: 'MEMBER',
name: string;
point: number;
};
type Guest = {
tag: 'GUEST',
name: string;
visitCount: number;
};
type User = Admin | Member | Guest;
function login(user: User) {
switch (user.tag) {
case 'ADMIN' : {
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
break;
}
case 'MEMBER' : {
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
break;
}
case 'GUEST' : {
console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다.`);
break;
}
}
}
하나의 예를 더 살펴보자.
/**
* 복습 겸 한가지 더 사례
*/
// 비동기 작업의 결과를 처리하는 객체
type AsyncTask = {
state: 'LOADING' | 'FAILED' | 'SUCCESS';
error?: {
message: string;
};
response?: {
data: string;
}
};
// 로딩 중 -> 콘솔에 로딩중 출력
// 실패 -> 실패 : 에러 메시지 출력
// 성공 -> 성공 : 데이터 출력
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;
}
}
}
const loading: AsyncTask = {
state: 'LOADING',
};
const failed: AsyncTask = {
state: 'FAILED',
error: {
message: '오류 발생 원인은 ~~',
}
};
const success: AsyncTask = {
state: 'SUCCESS',
response: {
data: '데이터~~',
}
};
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;
}
}
}
const loading: AsyncTask = {
state: 'LOADING',
};
const failed: AsyncTask = {
state: 'FAILED',
error: {
message: '오류 발생 원인은 ~~',
},
};
const success: AsyncTask = {
state: 'SUCCESS',
response: {
data: '데이터~~',
},
};
tag를 붙여서 객체들을 완벽히 구별해주어서 'Taged 유니온 타입'이라고도 함