
type Person = {
name: string;
age: number;
};
let person:Person = {};
person.name "제노";
person.age = 27;
빈 객체로 초기화 하고 타입을 지정해버리면, person에는 name, age가 없어서 오류가 발생
나중에 name, age를 초기화 해주고 타입 정의를 해서 타입 정의를 지우면, 초기화에서 오류가 발생
→ 이는 빈 객체를 기준으로 추론하기 때문에 빈 객체가 되어 오류가 발생한다.
let person = {} as Person;
person.name = "제노";
person.age = 27;
⇒ 위와 같이 as 연산자를 써주면 된다.
또 다른 예시
type Dog = {
name: string;
color: string;
};
let dog: Dog = {
name: "돌돌이",
color: "brown",
breed: "진도",
};
// 초과 프로퍼티가 발동하여 breed에서 오류 발생
// 근데 breed를 꼭 써야 하는 경우
초과 프로퍼티가 발동하여 breed에서 오류가 발생
근데 꼭 breed를 써야하는 경우에는 다음과 같이 as를 사용해주면 된다.
let dog = {
name: "돌돌이",
color: "brown",
breed: "진도",
} as Dog;
- 값 as 단언 ← 단언식
- A as B
- A가 B의 슈퍼타입이거나
- A가 B의 서브 타입이어야 한다.
let num1 = 10 as never;
let num2 = 10 as unknown;
let num3 = 10 as string;
// A는 number, B는 string타입, 서로 겹치는 값이 없어서 교집합이 없는 경우
// 즉, 서로가 슈퍼타입, 서브타입이 아니라서 오류가 발생한다.
예시1
let num4 = 10 as const;
// number literal type 10으로 추론이 된다.
예시2
let cat = {
name: "야옹이",
color: "yellow",
} as const;
// 모든 프로퍼티가 읽기 전용으로 된 객체로 추론된다.
cat.name = "";
// 즉, 이렇게 프로퍼티 값을 수정할 수 없는 객체가 된다.
// 따라서, 프로퍼티에 모두 readonly를 사용하지않고 const 단언을 하면 된다.
⇒ 어떤 값이 null이거나 undefined가 아니라고 알려주는 역할
type Post = {
title: string;
author: string;
};
let post: Post = {
title: "게시글1",
author: "제노",
};
const len: number = post.author?.length;
// ? : js에서 제공하는 옵셔널 체이닝
// null이거나 undefined일 경우 값 자체를 undefined가 나오게 하는 것
// 값 자체가 undefined가 될 수 있어서 number 타입으로 정의한 값에 undefined가 들어갈 수 없기 떄문에 오류가 발생
const len: number = post.author!.length;
// !를 쓰면 오류 해결
// !를 사용하면 이 값이 null이거나 undefined가 아닐 것이라고
// ts 컴파일러가 믿도록 만드는 것
⇒ 조건문 등을 이용해 넓은 타입에서 좁은 타입으로 좁히는 것
⇒ 타입을 상황에 따라 좁히는 방법을 이야기한다.
예시1(number, string)
// value => number: toFixed 적용
// value => string: toUpperCase 적용
function func(value: number | string) {
value;
value.toUpperCase();
value.toFixed();
// number | string이기 때문에 오류 발생
if (typeof value === "number") {
console.log(value.toFixed());
// 이 조건문 내에서는 number 타입임이 보장됌
} else if (typeof value === "string") {
console.log(value.toUpperCase());
// 이 조건문 내에서는 string 타입임이 보장됌
}
// 타입을 좁힐 수 있는 표현을 ts에서는 type guard라고 부른다.
}
예시2(Date, null 타입 추가)
// value => number: toFixed 적용
// value => string: toUpperCase 적용
// value => Date: getTime
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());
}
// typeof는 null값에 적용해도 object를 똑같이 반환해서 null이 있으면 오류가 발생함
}
예시2 오류 해결
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 사용
// 왼쪽에 있는 값이 오른쪽의 인스턴스냐 라고 묻는 문법
// value값의 타입이 Date객체일 것이 보장되기 때문에 Date 객체로 추론되며 오류가 해결된다.
}
예시3
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 instanceof Person) {
// 오류 발생
// instanceof는 우측에 있는 항에 타입이 들어오면 안된다.
// 클래스가 아니기 때문에, instanceof 연산자 뒤에 사용할 수 없다.
// in 연산자 사용
}
}
예시3 오류 해결
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}살 입니다.`);
}
}
// in 연산자와 && 연산자를 사용해줘서 해결해주면 된다.
⇒ 교집합이 없는 타입들로만 만든 유니온 타입을 말함.
ex) number 타입과 string 타입이 있다. ⇒ number | string
⇒ 이를 서로소 집합이라고 부른다.
⇒ 코드가 직관적이지 않을 경우에 사용된다.
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) {
// admin 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
} else if ("point" in user) {
// member 타입
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
} else {
// guest 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문하셨습니다.`);
}
// 이렇게 코드를 작성하면 조건문만 보고 어떤 타입을 취급하는지 직관적으로 알 수 없다.
}
// 이럴때 서로소 유니온 타입을 사용한다.
type Admin = {
tag: "ADMIN";
name: string;
kickCount: number;
};
type Member = {
tag: "MEMBER";
name: string;
point: number;
};
type Guest = {
tag: "GUEST";
name: string;
visitCount: number;
};
위와 같이 모든 타입에 tag 프로퍼티를 추가해서 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) {
if (user.tag === "ADMIN") {
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다.`);
} else if (user.tag === "MEMBER") {
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다.`);
} else {
console.log(`${user.name}님 현재까지 ${user.visitCount}번 방문하셨습니다.`);
}
}
// 훨씬 직관적으로 코드를 짤 수 있다.
// 객체 타입에 각각 string literal로 정의된 프로퍼티드을 다르게 정의해주면
// 서로소 유니온 타입으로 만들 수 있기 때문에, 직관적으로 타입을 좁혀서 처리할 수 있다.
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;
}
}
}
// 좁혀질 타입이 없으므로 옵셔널 체이닝을 써주거나 !를 써줘야 한다.
// 안전한 코드가 아니므로, AsynTask를 3개의 타입으로 서로소 유니온 타입으로
// 만들어주면 된다.
코드
type LoadingTask = {
state: "LOADING";
};
type FailedTask = {
state: "FAILED";
error: {
message: string;
};
};
type SuccessTask = {
state: "SUCCESS";
response: {
data: string;
};
};
이렇게 타입을 3개로 분리해주고, state를 통해 분류할 수 있도록 한다.
전체 코드
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;
}
}
}