1. 타입은 집합이다
타입스크립트의 '타입'은 사실 여러개의 값을 포함하는 '집합'입니다.
그리고 이러한 타입(집합)들은 수직관계를 가지고 있습니다
let num1:number =10;
let num2: 10 = 10;
num1 = num2;
특별히 서브 타입의 값을 슈퍼 타입의 값으로 취급하는 것은 업 캐스팅 이라고 부르고 반대는 다운캐스팅이라고 부릅니다.
따라서 쉽게 정리하면 업캐스팅은 모든 상황에 가능하지만 다운 캐스팅은 대부분의 상황에 불가능하다고 할 수 있습니다.
2. 타입계층도와 함께 기본타입 살펴보기
/**
* unknown 타입 => 모든 타입 중 가장 슈퍼타입(전체집합 개념)
*/
function unknownExam(){
let a:unknown =1; //언노운에 캐스팅되는거 가능(업캐스팅)
let b:unknown ="hello";
let c:unknown = true;
let d:unknown = null;
let e:unknown = undefined;
let unknownVar:unknown;
//let num:number = unknownVar; // 언노운이 캐스팅되는거 불가능(다운캐스팅)
//let str:string = unknownVar;
//let bool:boolean = unknownVar;
}
// 모든 타입들을 unknown에 업캐스팅할 수 있다.
/*
* Never 타입 (모든 집합의 서브 집합, 공집합과 같은 개념)
*/
function neverExam(){
function neverFunc(): never{
while(true){}
}
let num:number = neverFunc(); // never타입이 모든타입에 캐스팅되는거 가능(업캐스팅)
let str:string = neverFunc();
let bool:boolean = neverFunc()
//let never1: never = 10; // 다른 타입들이 never타입에 캐스팅되는것 불가능(다운 캐스팅)
//let never1:never = "string";
//let never3: never = true;
}
/*
* Void 타입
*/
function voidExam (){
function voidFunc(): void{
console.log("hi")
return undefined; // undefined가 void에 업캐스팅된것과 같은 것
}
let voidVar: void = undefined; //undefined가 void에 업캐스팅
}
/**
* any 타입(치트키 타입, 수직 계층도 무시)
* 사용하지 않는 것을 추천
*/
//못하는 건 딱하나 never 타입에 값을 할당하는 것!, never타입에 어떤 타입도 할당할 수 없다
function anyExam(){
let unknownVar: unknown;
let anyVar:any;
let undefinedVar: undefined;
let neverVar: never;
anyVar = unknownVar; //(다운캐스팅이지만 any 타입과 함께 라서 가능)
undefinedVar = anyVar;//(다운캐스팅이지만 any 타입과 함께 라서 가능)
//neverVar = anyVar; // 불가능, 이유는 neverVar가 공집합 이기 때문에 어떤 타입도 할당할 수 없음
}
3. 객체타입의 호환성
/**
* 객체 타입간의 호환성
* => 어떤 객체 타입을 다른 객체타입으로 취급해도 괜찮은가?
*/
type Animal = {
name: string;
color: string;// 2개만 있으면 Animal 타입, 더 넓은 개념(슈퍼)
}
type Dog = {
name: string;
color: string;
breed: string; // 추가, 더 좁은 개념 (서브)
}
let animal: Animal ={
name: "기린",
color:"yellow",
}
let dog: Dog ={
name: "뽀미",
color: "white",
breed: "스피츠",
}
animal = dog; // 업캐스팅
//dog = animal; // 다운캐스팅 (불가)
//객체타입들도 서로 다른 슈퍼/서브 타입을 가진다
//조건(프로퍼티)가 적을수록 더 넓은 개념, 상위, 슈퍼 타입, 반대로 조건이 많을수록 구체적, 하위, 서브타입
type Book = {
name: string;
price: number; //슈퍼타입
};
type Programming = {
name: string;
price: number;
skill:string; //서브타입
};
let book: Book;
let ProgrammingBook: Programming= {
name: "한입크기타입스크립트",
price: 33000,
skill: "reactjs"
}
book = ProgrammingBook;
//ProgrammingBook = book; // 다운캐스팅(불가)
/**
* 초과 프로퍼티 검사 (딱 객체타입에서 정의된 프로퍼티만 넣도록)
*/
let book2: Book= {
name: "한입크기타입스크립트",
price: 33000,
//skill: "reactjs" // 위에 처럼 book에 ProgrammingBook을 할당했는데 왜 안되지?
}
let book3: Book = ProgrammingBook; //처음에 정의된 객체 프로퍼티를 지키기 때문에 굳
//함수의 인자도 type 설정 가능
function func(book: Book){
func({
name: "한입크기타입스크립트",
price: 33000,
//skill: "reactjs" //처음에 정의된 객체 프로퍼티를 넘어섬, 정의안된 프로퍼티씀 (오류)
})
}
4. 대수타입
/**
* 대수타입
* 여러개의 타입을 합성해서 새롭게 만들어낸 타입
합집합 타입과 교집합 타입이 존재*/
/**
* 1. 합집합 - Union(|) 타입
*/
let a: string | number | boolean;
a= 1;
a= "hello";
a= true;
let arr:(number| string| boolean)[] = [1,"hello",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:"",
}
let union4: Union1 ={
name:"" // Dog 타입 조건에도 맞지 않고, Person 타입조건에도 맞지 않는, 외부 타입
}
/**
* 2. 교집합 타입 - Intersection(&) 타입
*/
let variable:number & string; // let variable: never(공집합)
//기본타입을 가지고 인터섹션 타입을 구하면 거의 never,
//그래서 인터섹션은 거의 객체타입에 많이 씀
type Dog ={
name: string;
color: string;
};
type Person ={
name:string;
language:string;
}
type Intersection = Dog & Person; // 두타입을 모두 만족시키는 키와 값이 필요
let intersection:Intersection = {
name: "",
color: "",
language:"" // 하나라도 빼먹으면 오류가 난다
}
5. 타입추론
/**
* 타입추론 => 알면, 코드가 간결해짐
*/
//점진적 타입시스템
let a =10; // 초기값을 넣어주기만 하면 초기값 기반으로 타스가 타입추론
let b = "hello";
let c ={
id:1,
name:"정지은",
profile:{
nickname:"winterlood",
},
urls:["https://"],
}
//객체와 배열의 구조분해할당
let {id, name, profile} = c;
let [one, two, three] = [1, "hello", true];
function func(message = "hello"){
return "hello"; // 함수는 초기값이 아닌 리턴문으로 타입추론
}
//당황(?)스러운 추론
// 암묵적 any 타입 => any 타입의 진화
//암묵적 any 타입 => let d:any (명목적 any타입)이랑은 다름
let d; //any
d = 10; //number
d.toFixed(); //number
d = "hello"; //string
d.toLowerCase(); //string
const num =10; // 리터럴(값) 타입으로 추론 const num: 10
let arr = [1, "string"];
//let arr:(number | string)[]
// 최적의 공통타입으로 추론해줌
6. 타입단언
값 as 타입 ({} as Person)으로 특정 값을 원하는 타입으로 단언할 수 있습니다. 이를 타입 단언 이라고 부릅니다.
/**
* 타입단언 (as)
*/
type Person = {
name: string;
age: number,
}
let person = {}
person.name = "이정환"; // '{}' 형식에 'name' 속성이 없습니다.
person.age = 27; // '{}' 형식에 'name' 속성이 없습니다.
let person2 = {} as Person; // let person2: Person
person2.name = "정지은"
person2.age = 1
type Dog = {
name: string;
color: string;
}
let dog: Dog = {
name:"뽀미",
color:"흰색",
breed: "스피츠", //'Dog' 형식에 'breed'이(가) 없습니다.
}
let dog2 = {
name:"뽀미",
color:"흰색",
breed: "스피츠", //'Dog' 형식에 '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; // 오류, 두 형식이 서로 충분히 겹치지 않기 때문입니다.
let num4 = 10 as unknown as string ; // 치트키, 다중단언 권장 X
/**
* const 단언
*/
let num5 = 10 as const //const로 선언한 것과 같은 효과
let cat = {
name: "야옹이",
color: "yellow",
} as const
/*
바로 위와 같음
type const = {
readonly name: "야옹이";
readonly color: "yellow";
}*/
cat.name = '' //읽기 전용 속성이므로 'name'에 할당할 수 없습니다.
/**
* Non Null 단언
*/
type Post = {
title : string;
author?: string;
}
let post:Post = {
title: "게시글1",
author: "정지은",
}
const authorlen: number = post.author?.length; //'number | undefined' 형식은 'number' 형식에 할당할 수 없습니다
//? 는 옵셔널 체이닝 , post.author이 없으면 undefined를 만들어줌, 있으면 number
const authorlen2: number = post.author!.length; // !는 있어의 느낌, 있어!
7. 타입 좁히기(조건문)
타입 좁히기란 조건문 등을 이용해 넓은타입에서 좁은 타입으로 타입을 상황에 따라 좁히는 방법을 이야기 한다
타입 좁히기에는 타입 가이드를 사용할 수 있다
/**
* 타입 좁히기
* 조건문 등을 이용해 넓은타입에서 좁은 타입으로
* 타입을 상황에 따라 좁히는 방법을 이야기
*/
//타입 좁히기에는 타입 가이드를 사용할 수 있음
// value => number : toFixed
// value => string : toUpperCase
// value => Date : getTime
// value => Person: name은 age살입니다
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()) //'value'은(는) 'null'일 수 있습니다.
}
}
type Person = {
name: string;
age: number;
};
//타입별 타입가이드
function func2(value: number| string | Date | null | Person){
if(typeof value === "number"){ //기본타입 : typeof
console.log(value.toFixed())
}else if(typeof value === "string"){ //기본타입 : typeof
console.log(value.toUpperCase)
}else if(value instanceof Date){ //클래스 : instanceof
console.log(value.getTime()) // instanceof는 Date와 같은 클래스에 쓴다
}else if(value && "age" in value){ //타입별칭 : "prop" in value
//value: Person, value 라는 값에 age가 있으면 true를 반환하고, 아니면 false를 반환
console.log(`${value.name}은 ${value.age}살 입니다`)
} //Date는 자바스크립트 내장 클래스니까 ok, 그런데 Person은 클래스가 아닌, 객체 타입이라 instanceof 못씀
}
8. 서로소 유니온 타입
유니온 타입은 서로 교집합이 없는 타입을 말한다
tag 나 state 같은 프로퍼티에 "SUCCESS" 같은 스트링 리터럴 타입을 붙여주면, 따로 노는 서로소 집합이 된다.
따라서 값을 할당할때 개발자가 구별하기가 쉬워진다
/**
* 서로소 유니온 타입
* 교집합이 없음
* 교집합이 없는 타입들로만 만든 유니온 타입을 말함
*/
//tag를 붙여주면 서로 포함관계가 있던 3집합이
//서로 따로노는 서로소 집합이 되버린다
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;
//Admin => {name}님 현재까지 {kickCount}명 강퇴했습니다
//Member => {name}님 현재까지 {point}모았습니다
//Guest => {name}님 현재까지 {visitCount}번 오셨습니다
function login1(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}번 오셨습니다`)
}
}
// 이렇게 하면 타입별로 구분이 잘안됨, 그래서 구별을 하기 위해서 유니온 타입을 쓰는 거임
function login2(user: User){
if(user.tag === "ADMIN"){
//Admin 타입
console.log(`${user.name}님 현재까지 ${user.kickCount}명 강퇴했습니다`)
}else if(user.tag === "MEMBER"){
//Member 타입
console.log(`${user.name}님 현재까지 ${user.point} 모았습니다`)
}else {
//Guest 타입
console.log(`${user.name}님 현재까지 ${user.visitCount}번 오셨습니다`)
}
}
//switch(user.tag) 문을 사용할 수 있다
function login3(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;
}
}
}
/**
* 복습겸 한가지 더 사례
*/
//비동기 작업의 결과를 처리하는 객체
// 3가지 객체의 타입을 정의하는 타입별칭!
type AsyncTask = {
state: "LOADING" | "FAILED" | "SUCCESS";
error?:{
message: string;
};
reponse?: {
data: string;
};
}
// 로딩중 => 콘솔에 로딩 중 출력
// 실패 => 실패: 에러메세지 출력
// 성공 => 성공: 데이터를 출력
// 아래 함수는 한가지 타입인 AsyncTask를 사용한 경우, ? 등 선택적 프로퍼티가 사용되어 안전하지 못한 코드
function processResult(task:AsyncTask){
switch(task.state){
case "LOADING":{
console.log("로딩중")
break;
}
case "FAILED": {
console.log(`실패 : ${task.error?.message}`) //(property) error?: {message: string} | undefined
break;
}
case "SUCCESS":{
console.log(`성공 : ${task.reponse?.data}`)
break;
}
}
}
//위의 함수처럼 한 타입에 퉁치는 것이 아니라 각각 타입별칭을 만들어보자
// 각 타입은 서로 겹치지 않는 서로소
type LoadingTask = {
state: "LOADING";
};
type FailedTask = {
state:"FAILED";
error : {
message: string;
};
};
type SucessTask = {
state: "SUCCESS";
reponse: {
data: string;
}
}
const loading: AsyncTask ={
state: "LOADING",
}
const failed: AsyncTask = {
state: "FAILED",
error:{
message: "오류발생 원인은 ~"
},
}
const success: AsyncTask = {
state: "SUCCESS",
reponse: {
data: "데이터~"
},
}
동시에 여러가지 상태(비동기적 처리)를 표현해야 하는 객체의 타입의 경우에는 선택적 프로퍼티(?)를 사용하는 것보다는 상태(EX.state:"SUCCESS")에 따라 타입을 쪼개서 서로소 유니온 타입으로 만드는 것이 좋다
'typescript' 카테고리의 다른 글
속성이 다른 데이터들을 같은 구성으로 렌더링하기 (4) | 2023.07.23 |
---|---|
함수와 타입 (2) | 2023.06.01 |
타입스크립트 기본2 (타입별칭, 인덱스 시그니처, Enum, Any, Unknown, Void, Never 타입) (0) | 2023.05.31 |
타입스크립트 기본(원시타입, 비원시타입, 리터럴 타입) (0) | 2023.05.31 |
타입스크립트의 점진적 타입시스템 (0) | 2023.05.31 |