본문 바로가기
Typescript

[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 9 ~ 13

by 맨날개발 2025. 6. 8.
이펙티브 타입스크립트 읽고 정리
아이템 1 ~ 5 보러가기
아이템 6 ~ 8 보러가기

 

9️⃣ 아이템 9. 타입 단언보다는 타입 선언을 사용하기

값에 타입을 부여하는 방법은 두가지가 존재한다.

  • 타입 단언 : 변수 선언 시 타입을 지정하는 방법
  • 타입 선언 : 값에 타입을 지정하는 방법

타입 선언을 사용하는 경우 변수에 값 할당 시 해당 값이 지정된 타입을 만족하는지를 체크한다.

타입 단언은 타입스크립트가 추론한 타입을 무시하고 지정한 타입으로 강제로 지정하는 것이다.

 

아래에서 보듯이 동일한 {} 값을 변수에 할당할 때 person1에는 타입에러가 발생하고 person2는 타입에러가 발생하지 않는다. {} 는 Person이 아니기 때문에 타입 에러가 발생해야한다.

 

하지만 단언에서는 발생하지 않기 때문에 추후 문제가 발생할 여지가 존재한다. 이것만 보더라도 단언보다는 선언을 사용하는 것이 좋다는 것을 알 수 있다.

interface Person {
  name: string;
}

const person1: Person = {};
const person2 = {} as Person

 

  속성을 맞지 않는 경우 값 할당 가능 여부  잉여 속성 체크
타입 단언 O X
타입 선언 X O

 

타입 단언 또한 항상 사용이 가능한 건 아니다. 타입 단언을 사용할 수 있는 건 넓은 범위에서 더 좁은 범위로 할당이 가능하다.

 

위의 코드에서 {} as Person이 가능한 이유이다. Person은 구체적인 name 속성을 가져야 하기 때문에 더 좁은 범위라고 할 수 있다.

 

하지만 서로 포함관계에 있지 않는 경우에는 단언을 사용할 수 없다. 아래의 코드를 보면 Person, Car는 address 라는 공통 속성이 존재하기는 하나 서로가 서브타입이 아니기 때문에 단언을 사용할 수 없다.

interface Person {
    address: string;
    age: number;
}

interface Car {
    wheel: string;
    address: string;
}

const car: Car = { wheel: '바퀴', address: '주소' }
const person1 = car as Person;

 

배열의 map 메서드를 사용할 때 반환되는 값에 타입을 지정하고 싶다면 단언을 사용하지 말고 반환 타입을 지정하는게 좋다.

['hello'].map((name): Person => ({ name }))

 

타입 단언을 사용하는 대표적인 경우는 다음과 같다.

  • DOM 요소에 타입 할당
  • 서버 응답

 

🔟 아이템 10. 객체 래퍼 타입 피하기

원시타입은 객체 래퍼 타입에 할당이 가능하지만 객체 래퍼 타입은 원시타입에 할당이 불가능하다. 자바스크립트에서도 객체 래퍼를 통해 값을 생성하는 지양하고 있고 아래와 같이 객체 래퍼 타입은 원시 타입에 할당이 불가능하기 때문에 원시 타입만을 사용하는 것이 좋다.

const str1: String = new String('str1');
const str2: string = str1;

const str3 = 'str3';
const str4: String = str3;

 

 

1️⃣1️⃣ 아이템 11. 잉여 속성 체크의 한계 인지하기

변수에 리터럴 객체를 할당할 때 잉여 속성을 체크하게 된다.

interface Person {
    name: string;
}

const p: Person = {
    name: 'hello',
};

const person1: Person = {
    name: 'hello',
    age: 24,
};

const person2: Person = p;
  • person1은 리터럴 객체를 할당하기 때문에 잉여 속성을 체크하게 되고 잉여 속성이 존재하기 때문에 타입 에러가 발생한다.
  • person2은 변수를 할당하기 때문에 잉여 속성 체크를 하지 않아 타입 에러가 발생하지 않는다.
함수의 인자로 리터럴 객체를 전달할때도 동일하게 작동한다.

 

객체 리터럴은 코드를 작성하는 순간 이미 “최종 형태”가 결정되므로, 기대하지 않은 속성이 들어 있으면 실수일 가능성이 크다고 볼 수 있다. 그래서 함수 인자나 변수 초기값으로 직접 **객체 리터럴을 넘기면 리터럴에 정의된 모든 키가 대상 타입과 일치하는지 엄격히 비교한다.

 

 

1️⃣2️⃣ 아이템 12. 함수 표현식에 타입 적용하기

타입스크립트에서 함수 표현식을 사용하게 되면 전체 함수의 타입을 재사용할 수 있기 때문에 장점이 있다.

 

타입스크립트는 자바와 같은 엄격한 타입 체크를 하지 않기 때문에, 아래와 같이 재사용 가능한 타입을 정의해 놓으면 좋다.

type BinaryFn = (a: number, b: number) => number;

const add: BinaryFn = (a, b) => a + b;
const sub: BinaryFn = (a, b) => a - b;

 

같은 타입 시그니처를 반복하는 코드가 존재한다면 함수 타입을 분리해 공통으로 사용하면 좋다.

 

 

1️⃣3️⃣ 아이템 13. 타입과 인터페이스의 차이점 알기

타입스크립트에서 명명된 타입을 정의하는 방법은 두가지가 존재한다.

  • interface
  • type

 

대부분의 상황에서는 interface와 type 둘다 사용이 가능하다. 그래서 프로젝트 내에서는 동일한 상황에서는 동일한 방법으로 정의하는 것이 좋다.

 

interface에서 불가능하지만 type에서는 가능한것

  • 원시 타입 정의
  • 유니온 타입
  • 튜플 정의

 

type에는 불가능하지만 interface에서는 가능한것

  • 보강 기능
    • 동일한 이름의 인터페이스를 정의하면 속성을 확장할 수 있다.
    interface Person {
        name: string;
    }
    
    interface Person {
        age: number;
    }
    
    const person: Person = {
        name: 'hello',
        age: 24
    }
    
    • 하나의 프로젝트 내에서 보강 기능을 사용하면 타입이 여러곳에 흩어질 수 있어 사용에 혼란을 주게 된다. 보통은 다른 라이브러리를 사용할 때 라이브러리의 타입을 보강하는 경우 주로 사용하게 된다.