기록하는 개발자

[TypeScript] 타입 추론, 타입 호환, 타입 단언 본문

Web/TypeScript

[TypeScript] 타입 추론, 타입 호환, 타입 단언

밍맹030 2023. 5. 22. 17:05
728x90

타입 추론

변수 선언 시 타입을 쓰지않아도 컴파일러가 스스로 판단하여 타입을 넣어주는 것

 

타입을 추론하는 경우

1. 초기화된 변수

let num = 10;

num에 대한 타입을 따로 지정하지 않더라도 10으로 초기화 되었으므로 number로 간주된다.

2. 기본값이 설정된 매개변수

function cook (how = 'cut', ingredient: string): void {
  console.log(`${how} the ${ingredient}`);
}

변수 how는 변수 ingredient와 달리 "cut"으로 기본값이 설정된 매개 변수이다.

이는 typescript에 의해 string으로 타입 추론 되므로 

how : string = "cut" 과 같이 작성할 필요가 없다.

3. 반환 값이 있는 함수

function sum(a: number, b: number) { 
  return a+b; 
};

const result = sum(1, 2);

 

두 number 타입 변수의 연산 결과는 number임이 확실하므로

이 정도 수준의 타입 추론은 typescript가 알아서 해준다.

 

이렇게 타입을 생략한채 변수를 선언하거나 초기화 할 때 타입이 추론된다.

이외에도 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때 타입 추론이 일어난다.

 

타입 호환

타입스크립트 코드에서 특정 타입 간에 구조가 비슷하면 타입 호환이 될 수 있다.

 

아래 Cook과 Bake는 서로 다른 자료형임에도 에러 없이 잘 호환이 된다.

interface Cook {
  name: string;
}

class Bake {
  name: string;
}

let i: Cook;
i = new Bake();

 

 

 

구조적 타입 호환

객체 리터럴 ({ name: "Ramen", location: "Japan" }) 을 변수(noodle) 에 직접 할당하거나 인수로 전달할 때 초과 프로퍼티 검사(객체 속성 검사)를 받게 된다. 

객체 리터럴이 대상 타입(target type : Food)이 갖고 있지 않은 프로퍼티(country)를 갖고 있으면 당연히 에러가 발생한다.

interface Food{
  name: string;
}

let noodle: Food;
noodle = { name: "Ramen", country : "Japan" }; 

//아래서 다룰 타입 단언 방법으로 해결 가능
noodle = { name: "Ramen", country : "Japan" } as Food;

 

객체를 다른 변수(menuA)에 할당하면, 변수는 초과 프로퍼티 검사를 받지 않기 때문에 에러가 발생하지 않는다.

menuB와 같이 인터페이스에 정의된 공통 객체 프로퍼티가 없으면 무조건 에러가 발생한다.

interface Food{
  name: string;
}

let noodle: Food;

let menuA = { name: "Spaghetti", country: "Italy" }; 
noodle = menuA; 

let menuB = { food: "Pasta", country: "Italy" }; 
noodle = menuB;

 

union 타입 호환

연산자가 and 에서 or로 바뀌었으니 unionType에서는 두 객체 중 하나만 선언해야 할 것 같다는 생각이 드는데

실제로 and 연산이 아님에도 두 객체를 동시에 선언해도 문제가 없다.

이것이 바로 union 타입 호환이다.

type normalType = { num: number } & { str: string };
// 두 속성이 다 있을 때 가능
const a: normalType = { num: 1, str: '1' }; 
const b: normalType = { num: 1 }; // Error
const c: normalType = { str: '1' }; // Error

type unionType = { num: number } | { str: string };
// 두 객체 속성중에 하나만 있어도 되고 두개 모두 있어도 된다
const d: unionType = { num: 1, str: '1' };
const e: unionType = { num: 1 };
const f: unionType = { str: '1' };

interface 타입 호환

똑같은 인터페이스명을 중복으로 선언하면 안의 인스턴스들이 서로 합쳐져 호환된다. 마치 컴파일러가 알아서 자동으로 하나로 합쳐준다고 생각하면 된다.

interface A {
   boil: () => {...};
}
interface A {
   stir: () => {...};
}
interface A {
   chop: () => {...};
}

const cook: A = {
   boil() { ... },
   stir() { ... },
   chop() { ... },
};

enum 타입 호환

enum 타입은 number 타입과 호환되지만 enum 타입끼리는 호환되지 않는다.

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let statusNow = Status.Ready;
statusNow = 0;
statusNow = Color.Green;  // Error

function 타입 호환

함수 끼리도 타입 호환은 호출하는 시점에 문제가 없어야 할당이 가능하다.

 

 함수 타입 A가 함수 타입 B로 할당 가능하기 위한 조건

1. A의 매개변수 개수가 B의 매개변수 개수보다 적어야 한다.

type F1 = (name: string, age: number) => string;
type F2 = (age: number) => string;

let f1: F1 = (name, age) => `name : ${name}, age : ${age}`;
let f2: F2= (name) => `name : ${name}`;

f1 = f2; // f1 보다 f2 매개변수가 더 적기 때문에 가능
f2 = f1; // ERROR

 

2. 같은 위치의 매개변수에 대해 B의 매개변수가 A의 매개변수로 할당 가능해야 한다.

type F3 = (id: string, age: number) => string;
type F4 = (id: string | number, age: number) => string;

let f3: F3 = (id, age) => `id : ${id}, age : ${age}`;
let f4: F4 = (id, age) => `id : ${id}, age : ${age}`;

f3 = f4; // f4의 매개변수 b가 union type이므로 f3에도 할당 가능
f4 = f3; // ERROR

 

3. A의 반환값은 B의 반환값으로 할당 가능해야 한다.

type F5 = (age: number) => string;
type F6 = (age: number) => number | string;

let f5: F5 = (age) => `age : ${age}`;
let f6: F6 = (age) => (age >= 20? 'you are not an adult' : age);

f6 = f5; // f5는 문자열을 반환하고, f6는 문자열을 반환할 가능성이 있기 때문에 할당이 가능
f5 = f6; // ERROR - 그러나 f5(1) 같이 정수가 반환될수 있기 때문에 불가능

Class 타입 호환

클래스 타입은 클래스 타입끼리 비교할 때 static member와 생성자(constructor)를 제외하고 속성만 비교한다.

class man {
   age: number;

   constructor(name: string, age: number) {
      this.age = age;
   }

   static phoneNumber: number;
}

class woman {
   age: number;

   constructor(age: number) {
      this.age = age;
   }

   static phoneNumber: string;
}

let Tom : man = new woman(25);
console.log('Tom : ', Tom.age); //25

let Olivia : woman = new man('Olivia', 26);
console.log('Olivia : ', Olivia.age); // 26

Generic 타입 호환

제네릭은 제네릭 타입 간의 호환 여부를 판단할 때, 타입 인자 <T> 가 속성에 할당 되었는지를 기준으로 한다.

interface Cook<T> {}

let x: Cook<number>;
let y: Cook<string>;
// 호환o
x = y;

interface Bake<T> {
  temperature : T;
}

let a: Bake<number>;
let b: Bake<string>;
//호환x
a = b;

타입 단언(Assertion)

타입 단언은 강제 형변환과는 다른 개념으로, 강제 형변환은 실제로 데이터 자료를 변환시키지만

타입 단언은 그냥 '타입이 이것이다'라고 컴파일러에게 명시할 뿐 실제로 데이터가 바뀌지는 않는다.

따라서 컴파일 과정에서 문제는 생기지 않지만(빨간줄 발생x), 실제 실행 시에는 오류가 발생할 수도 있다.

 

타입 단언을 선언하는 방법

1. 앵글 브라켓(angle-bracket, <>) 문법

2. as 문법

let str : unknown = "타입 단언을 선언해보자";

//assertion 변수의 타입을 string으로 단언 처리하기
// 1. 앵글 브라켓 문법
let assertion1 : number = (<string>str).length;

// 2. as문법
let assertion2 : number = (str as string).length;

 

 

참고

https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9D%EC%B2%B4-%ED%83%80%EC%9E%85-%EC%B2%B4%ED%82%B9-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0-%ED%83%80%EC%9E%85-%ED%98%B8%ED%99%98-%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8-%ED%83%80%EC%9E%85-%EA%B0%80%EB%93%9C-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC

https://velog.io/@sji7532/TypeScript-%ED%95%A8%EC%88%98%EC%9D%98-Type-%EC%84%A0%ED%83%9D%EC%A0%81-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B8%B0%EB%B3%B8-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98

728x90