본문 바로가기
개발공부

#3. 타입스크립트 이해하기

by 반류연 2025. 2. 20.

*이 게시물은 이정환 강사님의 '한 입 크기로 잘라먹는 타입스크립트' 강의를 수강한 뒤 정리한 글입니다.

 

타입 호환성

  • 서로 다른 타입이 있을 때, 각자를 해당 타입으로 취급해도 되는지 판단하는 것.
  • 타입간의 집합 관계도를 생각하면 이해하기 쉬움.
  • TS는 슈퍼타입의 값을 서브타입의 값으로 취급하는 걸 허용하지 않는다! (반대는 성립 가능)
    • ex) a: Number, b:Number Literal 일 때, b는 number로 취급할 수 있지만 a는 넘버리터럴 타입으로 취급할 수 없다!
    • 대부분의 경우엔 다운캐스팅을 허용하지 않는다.
let num1 : number = 10;
let num2 : 10 = 10;

num1 = num2 // 넘버리터럴 값을 넘버에 할당하겠다(업캐스팅) -> 가능
num2 = num1 // 넘버값을 넘버 리터럴에 할당하겠다(다운캐스팅) -> 호환 x, 오류!

 

타입계층도 사진(출처: 이정환 강사의 '한입크기로 잘라먹는 타입스크립트' 강의록)

 

※ 업캐스팅/다운캐스팅 여부가 헷갈릴 때는 이 타입계층도를 떠올리면 쉽다!

  • 모든 타입은 unknown 타입으로 업캐스팅 가능하다(=unknown 타입엔 어떤 값이든 담을 수 있다)
  • 모든 타입은 never 타입으로 다운캐스팅 할 수 없다. (= never에 값을 할당할 수 없다)
  • void는 undefined 값을 반환해도 괜찮지만, undefined 타입은 void 값을 반환할 수 없다
  • any는 계층구조도를 모두 무시한다! 그렇기에 최대한 쓰지 않는게 좋다

 

객체 타입의 호환성

  • 기본타입과 동일하게 객체타입의 호환성도 슈퍼-서브 간의 관계로 판단함.
  • 더 많은 프로퍼티를 가지고 있는 쪽이 서브! (부분 집합)
// 객체 타입간 호환성 예시

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 // 정상
dog = animal // 오류!

 

※ 업캐스팅이라도 초과 프로퍼티 검사가 발동하면 에러가 날 수 있다!

  • 초과 프로퍼티 검사란? : 객체 리터럴로 초기화 할 때 발동하는 TS 기능. 타입에 정의된 프로터피 외의 다른 초과 프로퍼티를 갖는 객체를 변수에 할당할 수 없게 막는 역할.
// 초과프로퍼티 예시

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

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

let book: Book = {
	name: "한 입 크기로 잘라먹는 타입스크립트",
    price: 33000,
    skill: "typescript" // 에러!
}
  • 해결 방법 : 객체 리터럴을 사용하지 않으면 됨 => 값을 별도의 변수에 보관한 다음, 그 변수값을 초기화 값으로 사용하면 됨.
// 초과프로퍼티 예시 - Ver. 에러 수정

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

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

// 1. 변수값 보관
let programmingBook: ProgrammingBook = {
	name: "한 입 크기로 잘라먹는 타입스크립트",
    price: 33000,
    skill: "타입스크립트"
}

// 2. 보관한 값으로 초기화
let book: Book = programmingBook;
// 함수의 매개변수에도 초과 프로퍼티 검사는 동일하게 발생
// 같은 방식으로 문제 해결

(...) 

function func(book: Book){}

func({
	name: "한 입 크기로 잘라먹는 타입스크립트",
    price: 33000,
    skill: "타입스크립트" // 에러! 
})


// 해결책
func(programmingBook);

 

 

대수타입

여러 타입을 합성하여 만든 타입.

 

1. 합집합(Union) 타입

  • '|' 를 사용하여 여러개의 타입을 정의함. (ex) let a = string | number
  • 합칠 수 있는 타입 갯수 제한 X
  • 배열, 객체 등 다방면으로 사용 가능함
// 유니온 배열 타입
// 배열에서 유니온 사용할 땐 타입정의 부분을 () 로 묶는다!

let arr: (number | string | boolean)[] = [1, "hello", false]


// 유니온 객체 타입
type Dog = {
	name : string;
    color: string;
}

type Person = {
	name : string;
    language : string;
}

type UnionEx: Dog | Person; // Dog 타입과 Person 타입의 합집합-> 모든 원소 사용 가능

let union1: Union1 = { // 정상
  name: "",
  color: "",
};

let union2: Union1 = { // 정상
  name: "",
  language: "",
};

let union3: Union1 = { // 정상
  name: "",
  color: "",
  language: "",
};

let union4: Union1 = { // 에러! 
  name: "",
};

 

2. 교집합(Intersection) 타입

  • '&' 를 사용하여 교집합 정의. (ex) let b = number & string
  • 기본 타입들은 보통 서로소 관계이기 때문에 교집합이 존재하지 않음 => never 타입으로 추론 됨
  • 위의 이유로 주로 객체 타입에서 사용되는 편
type Dog = {
	name: string;
    color: string;
}

type Person = {
	name: string;
    language: string;
}

type Intersection: Dog & Person;

// 질문
let intersection: Intersection = {
	name: "",
    color: "",
    language: ""
}

 

 

타입 추론

  • 타입이 정의되어 있지 않은 변수의 타입을 자동으로 추론하는 것.
  • 일반적으론 초기값을 기준으로 판단. 함수의 경우엔 return 값을 기준으로 판단.
  • '함수의 매개변수' 같이 일부 상황에서는 추론하지 못함
    -> any 타입으로 추론됨. 이 경우엔 strict 옵션에서 오류로 판단됨.
    -> 일반 변수의 타입이 any 로 판단될 경우에는 strict 옵션에서 걸리지 않음.
  • let 과 const에 따라 추론되는 타입이 다름
    • let num1 = 10 ->  number 타입으로 추론
    • const num2 = 10 -> number literal 타입으로 추론
  • 배열의 경우, 다양한 타입값을 담을 시 최적의 공통 타입(Best Common Type) 으로 추론됨
    • let arr = [1, 'string'] -> (string | number)[] 값으로 추론

 

타입 단언

  • 특정 값을 원하는 타입으로 단언하는 기능. '값 as 타입' 으로 정의
  • 사용 조건: A as B의 형태일 때, A와 B는 호환되어야 한다. (number as string -> 에러!)
  • 어떨 때 사용할까? 
    • 타입 정의한 객체의 초기값을 빈 값으로 두고 싶을 때
    • 초과 프로퍼티 검사를 피할 때
// 1. 타입 정의한 객체의 초기값을 빈 값으로 두고 싶을 때
type Person = {
	name: string;
    age: number;
}

// Person 타입은 빈 객체가 아니므로 오류가 발생한다
let person: Person = {}; //에러
person.name = "";
person.age = 23; 


// 타입 단언 사용
let person {} as Person; // 정상
person.name = "";
person.age = 23



// 2. 초과 프로퍼티 검사를 피할 때
type Dog = {
	name: string;
    color: string;
}

let dog: Dog = {
	name: "돌돌이",
    color: "brwon",
    breed: "진도"  
    // Dog 타입에는 없는 프로퍼티. 하지만 타입 단언을 사용했기 때문에 초과 프로퍼티 검사에 걸리지 않는다.
} as Dog

 

다중 단언 (사용 비추천)

let num = 10 as unknown as string
  • 단언 순서: 왼쪽에서 오른쪽. 
  • 실제로 값을 해당 타입으로 변환시키는 게 아닌 단순 눈속임으로, 슈퍼-서브 관계가 아닌 타입 단언 시 오류가 발생할 수 있다! 

const 단언

  • 타입 단언때만 사용할 수 있는 형식.
  • 특정 값을 const 타입으로 단언하면 변수를 const로 선언한 것 처럼 타입이 변경됨.
let num = 10 as const;
// num 의 타입은 10 Number Literal 으로 단언됨.

let cat = {
	name : "야옹이",
    color: "yellow"
} as const
// 모든 프로퍼티가 readonly 를 갖도록 단언됨

 

Non Null 단언

  • 값 as 타입 의 형태를 따르지 않는 단언.
  • 값 뒤에 !를 붙이면 해당 값을 undefined나 null이 아닐 것으로 단언할 수 있음
type Post = {
	title: string;
    author?. string;
}

let post: Post = {
	title: "게시글1"
}

const len: number = post.author!.length
// post.author 값은 null이나 undefined가 아니다!

 

 

타입 좁히기

  • 유니온 타입으로 선언했을 경우, 각 타입에 맞는 메소드를 오류 없이 사용할 수 있도록 타입을 좁혀주는 것.
  • 조건문과 typeof 를 사용. 이런 형태를 '타입 가드'라고 부름.
function func(value: number | string) {
	if(typeof value === "number") {
    	console.log(value.toFixed());
    }
    if(typeof value === "string"){
    	console.log(value.toUpperCase());
    }
}

 

1. instanceof 타입 가드

  • 내장 클래스 타입을 보장할 수 있는 타입가드 만드는 형식
  • 내장 클래스 또는 직접 만든 클래스에만 사용 가능. 직접 만든 타입에는 사용 X
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());
  }
}

2. in 타입 가드

  • 직접 만든 타입에 사용하고 싶을 때 사용하는 연산
// 직접 만든 타입
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}살 입니다`)
  }
}

 

 

서로소 유니온 타입

  • 서로소 관계(교집합 x)인 타입들을 모아 만든 유니온 타입
type Admin= {
	name: string;
    kickCount: number;
    tag: "ADMIN";
}

type Member = {
	name: string;
    point: number;
    tag: "MEMBER";
}

type Guest = {
	name: string;
    visitCount: number;
    tag: "GUEST";
}

type User = Admin | Member | Guset; // User는 '서로소 유니온 타입'

// 활용법
// 타입 좁히기의 일환으로 tag라는 리터럴 프로퍼티를 만들었음
function login(user:User){
	swith(user.tag){
    	case: "ADMIN": {
        	console.log(`${user.name}님, 현재까지 ${kickCount}명 퇴장시켰습니다.`);
        }
        case: "MEMBER"{
        	console.log(`${user.name}님, 현재까지 모은 포인트는 ${point}점 입니다.`)
        }
        case : "GUEST" {
        	console.log(`${user.name}님, 현재까지 ${name.visitCount}번 방문하셨습니다.`)
        }
    }
}

'개발공부' 카테고리의 다른 글

#6. 클래스  (0) 2025.02.27
#5. 인터페이스  (0) 2025.02.26
#4. 함수와 타입  (0) 2025.02.25
#2. 타입스크립트 기본  (0) 2025.02.18
#1. 타입스크립트 개론  (0) 2025.02.17