타입스크립트 타입 제어 기법 정리 / 타입 추론, 타입 단언, 타입 좁히기 등

타입 추론

변수 타입이 정의되지 않은 경우, 타입스크립트가 자동으로 변수 타입을 추론합니다.
타입스크립트는 변수의 초기값을 기준으로 타입을 추론합니다.
초기값이 없는 변수는 암묵적 any 타입이므로, 어떤 타입 값이든 할당할 수 있습니다.

타입 넓히기를 통한 타입 추론

// a : number 타입
let a = 10;

// a : 10 (number 리터럴 타입)
const a = 10;

타입스크립트는 타입 넓히기를 통해 더 범용적인 타입을 추론합니다.
const로 선언된 변수는 재할당이 불가하므로 리터럴 타입 그대로 추론됩니다.


타입 단언 (as)

타입스크립트 컴파일러에게 개발자가 타입을 지정해서 알려주는 기능입니다.

타입 단언 규칙
단언하는 타입이 값의 슈퍼 타입 또는 서브 타입이어야 타입 단언이 가능합니다.
다중 단언 시 타입 단언 규칙을 우회할 수 있지만, 안전하지 않아 권장되지 않습니다.

타입 단언으로 객체 정의 방법

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

// 초기값 {}에 name, age 속성이 없어서 타입 오류 발생
let person: Person = {};
person.name = "하지후";
person.age = 21;

// 타입 단언으로 객체 생성 후 ame, age 속성 부여
let person = {} as Person;
person.name = "하지후";
person.age = 21;

객체 뒤에 as 타입명을 붙이면 해당 타입으로 간주하도록 타입 단언을 할 수 있습니다.

타입 단언으로 초과 프로퍼티 검사 우회 방법

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

// User 타입에 없는 속성이 있어서 타입 오류 발생
let user: User = {
  name: "하영",
  age: 30,
  nickname: "하롱베이"
};

// User 타입 단언으로 초과 프로퍼티 검사 우회
let user = {
  name: "하영",
  age: 30,
  nickname: "하롱베이"
} as User;

변수에 객체 리터럴 타입 할당 시, 타입에 정의되지 않은 초과 속성이 있으면 오류를 발생시킵니다.
이러한 초과 프로퍼티 검사를 타입 단언으로 우회할 수 있으나, 안전하지는 않습니다.

Const 단언 방법

// number가 아니라, number 리터럴 타입으로 추론되도록 단언
let 변수명 = 10 as const;
// let 변수명: 10 = 10; 와 동일한 효과

// 객체의 모든 프로퍼티가 읽기 전용 속성으로 추론되도록 단언
let 변수명 = {
  count: 10,
  enabled: true
} as const;

Non Null 단언 방법

type Post = {
  title: string;
  author?: string;
}

let post: Post = {
  title : "게시글 제목",
  author: "송현주"
}

// 옵셔널 체이닝 문법
// author 프로퍼티가 없으면 자동으로 전체가 undefined 되는 연산자
// number 타입에 undefined 타입 할당 불가, 타입 오류 발생 
const len: number = post.author?.length;

// Non Null 단언 사용하여 타입 오류 제거
const len: number = post.author!.length;

옵셔널 프로퍼티 값이지만, 반드시 값이 있을 것이라고 단언할 수 있습니다.
실제로 값이 없으면 런타임 에러가 발생할 수 있으니 확실한 경우에만 사용해야 합니다.


타입 좁히기 (타입 가드)

조건문 등을 이용해 넓은 타입에서 좁은 타입으로 좁히는 기법입니다.
타입스크립트가 더 구체적인 타입을 추론하도록 도웁니다.

조건문을 이용한 타입 좁히기

function 함수명(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());
  }
}

매개변수 타입을 조건문으로 체크하면, 타입에 맞는 함수를 호출할 수 있습니다.

타입스크립트 타입 검사

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

function 함수명(value: number | string | Date | null | Person) {
  if (value && typeof value === "object" && "name" in value && "age" in value) {
    // Person 타입처럼 취급
  }
}

타입스크립트 타입은 런타임에 존재하지 않아 typeof로 검사할 수 없습니다.
in 연산자로 특정 프로퍼티가 있는지 검사하여 타입을 판별합니다.
타입에 tag 프로퍼티를 정의하고 value.tag를 검사하면 더 간결해집니다.

사용자 정의 타입 가드

type Dog = {
  name: string;
  isBark: boolean;
}

type Cat = {
  name: string;
  isScratch: boolean;
}

type Animal = Dog | Cat;

// 타입 가드 문법으로 타입 좁히기
function warning(animal: Animal) {
  if ("isBark" in animal) {
    // 강아지 타입 매개변수 처리
  } else {
    // 고양이 타입 매개변수 처리
  }
}

위와 같이, 타입 가드 문법으로 타입 좁히기가 완벽하게 되지 않는 경우
아래처럼 사용자 정의 타입 가드 함수를 사용하면 좋습니다.

type Dog = {
  name: string;
  isBark: boolean;
}

type Cat = {
  name: string;
  isScratch: boolean;
}

type Animal = Dog | Cat;

// 반환값이 true이면 좁은 타입으로 추론
function isDog(animal: Animal): animal is Dog {
  return 'isBark' in animal;
}

// 사용자 정의 타입 가드 함수로 타입 좁히기
function warning(animal: Animal) {
  if (isDog(animal)) {
    // 강아지 타입 매개변수 처리
  } else {
    // 고양이 타입 매개변수 처리
  }
}

사용자 정의 타입 가드 함수로 타입스크립트가 매개변수의 좁은 타입을 추론할 수 있도록 도와줍니다.