본문 바로가기
프로그래밍/TypeScript

타입스크립트 이해하기

by hi-rachel 2023. 10. 18.

타입스크립트 이해하기

타인은 집합이다

슈퍼타입(부모타입) > 서브타입(자식타입)

타입 계층도와 함께 기본타입 살펴보기

/**
 * unknown 타입
 */

function unknownExam() {
  // up cast
  let a: unknown = 1;
  let b: unknown = "hello";
  let c: unknown = true;
  let d: unknown = null;
  let e: unknown = undefined;

  let unknownVar: unknown;

  // down cast X
  // let num: number = unknownVar;
  // let str: string = unknownVar;
  // let bool: boolean = unknownVar;
}

/**
 * never 타입
 * 공집합
 */

function neverExam() {
  function neverFunc(): never {
    while (true) {}
  }

  let num: number = neverFunc();
  let str: string = neverFunc();
  let bool: boolean = neverFunc();

  //   let never1: never = 10;
  //   let never2: never = "string";
  //   let never3: never = true;
}

/**
 * void 타입
 * undefined의 슈퍼타입
 */

function voidExam() {
  function voidFunc(): void {
    console.log("hi");
    return undefined;
  }

  let voidVar: void = undefined;
}

/*
 * any 타입
 */

function anyExam() {
  let unknownVar: unknown;
  let anyVar: any;
  let undefinedVar: undefined;
  let neverVar: never;

  anyVar = unknownVar;

  // any - down cast 가능 => 타입 계층도 다 무시하는 타입
  undefinedVar = anyVar;

  anyVar = unknownVar;

  // 예외) never타입은 공집합이므로 어떤 타입도 down cast 할 수 없음
  //   neverVar = anyVar;
}

객체 타입의 호환성

/*
 * 객체 타입간의 호환성
 * => 어떤 객체타입을 다른 객체 타입으로 취급해도 괜찮은가?
 */

// 추가 property가 없는 type이 super type
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: "진도",
};

// down cast
animal = dog;

// up cast
// dog = animal;

type Book = {
  name: string;
  price: number;
};

type ProgrammingBook = {
  name: string;
  price: number;
  skill: string;
};

let book: Book;
let programmingBook: ProgrammingBook = {
  name: "타입스크립트",
  price: 10000,
  skill: "typescript",
};

/*
 * 초과 프로퍼티 검사
 */

book = programmingBook;

// programmingBook = book;

let book2: Book = {
  name: "타입스크립트",
  price: 10000,
  // skill: "typescript",
};

let book3: Book = programmingBook;

function func(book: Book) {}
func({
  name: "타입스크립트",
  price: 10000,
  //   skill: "typescript",
});

// 인수로 변수 전달
func(programmingBook);

대수 타입

/*
* 대수 타입
=> 여러 개의 타입을 합성해서 새롭게 만들어낸 타입
=> 합집합 타입과 교집합 타입이 존재
*/

/*
 * 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: "",
// }

/*
 * 2. 교집합 타입 - Intersection 타입
 */

// never 타입 => 공집합
let variable: number & string;

type Intersection = Dog & Person;

// Dog와 Person의 모든 property를 포함해야 교집합(&) 타입으로 포함될 수 있다
let intersection1: Intersection = {
  name: "",
  color: "",
  language: "",
};

타입 추론

/*
 * 타입 추론
 */

// let a: number = 10;

// 자동으로 변수 타입 추론
let a = 10;
let b = "hello";
let c = {
  id: 1,
  name: "rachel",
  profile: {
    nickname: "rachel_pink",
  },
  urls: [""],
};

let { id, name, profile } = c;

let [one, two, three] = [1, "hello", true];

// 매개 변수는 자동으로 타입 추론 x => 타입 명시해주지 않으면 오류 발생
// function func(param){
// }

// 함수의 반환값 추론 o
// => 추론할 정보가 있으면
function func1() {
  return "hello";
}

function func2(message = "hello") {
  return "hello";
}

// any 타입의 진화
let d;
d = 10;
d.toFixed();

d = "hello";
d.toUpperCase();

// const로 선언한 변수는 상수이기 때문에 number 리터럴, string 리터럴로 추론
const num = 10;
const str = "hello";

let arr = [1, "string"];

타입 단언

/**
 * 타입 단언(assertion)
 */

type Person = {
  name: string;
  age: number;
};

let person = {} as Person;
person.name = "";
person.age = 25;

type Dog = {
  name: string;
  color: string;
};

// 초과 프로퍼티 문제 해결됨
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;

// unknown으로 다중 단언을 하면 가능하지만 되도록 지양
let num3 = 10 as unknown as string;

/**
 * const 단언
 */

let num4 = 10 as const;

// 객체를 as const로 단언하면 모든 property를 readonly로 만듬
let cat = {
  name: "야옹이",
  color: "yellow",
} as const;

/**
 * Non Null 단언
 */

type Post = {
  title: string;
  // 옵셔녈 체이닝
  author?: string;
};

let post: Post = {
  title: "게시글1",
  author: "rachel",
};

// const len: number = post.author?.length;
const len: number = post.author!.length;

업 캐스팅 - 모든 상황에 가능
다운 캐스팅 - 대부분의 상황에 x

타입 좁히기

/**
 * 타입 좁히기
 * 조건문 등을 이용해 넓은 타입에서 좁은 타입으로
 * 타입을 상황에 따라 좁히는 방법을 이야기
 */

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}살 입니다`);
  }
}

서로소 유니온 타입

/**
 * 서로소 유니온 타입
 * 교집합이 없는 타입들로만 만든(서로소) 유니온 타입을 말함
 */

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 ("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}번 방문하셨습니다.`);
//   }
// }

// 주석이 없으면 직관적으로 알 수 없는 위 코드를 tag property를 추가해 변경
// tag를 추가하면 string 리터럴 타입이 추가되어 서로소 유니온 타입이 됨
function login(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문 이용
function login2(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 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: "데이터 ~~",
  },
};

 

 


[참고]

한 입 크기로 잘라먹는 타입스크립트

  • Section 3. 타입스크립트 이해하기 강의를 듣고 정리한 내용입니다.