타입 단언보다 타입 선언을

임효진·2023년 3월 21일
0

타입 단언보다는 타입 선언을 사용하기

타입스크립트에서는 변수에 값을 할당하고 타입을 부여하는 방법은 두가지다.

interface Person {name : string};
const alice : Person = {name : 'Alice'} //타입은 Person
const bob = {name : 'Bob'} as Person //타입은 Person

이 두가지 방법은 결과가 같아 보이지만 그렇지 않다,
첫 번째 alice:Person은 변수에 '타입 선언'을 붙여서 그 값이 선언된 타입임을 명시한다.
두 번째 as Person은 '타입 단언'을 수행한다. 그러면 타입스크립트가 추론한 타입이 있더라도
Person 타입으로 간주한다.
타입 단언보다 타입 선언을 사용하는 게 낫다. 그 이유는 아래 코드를 보자.

const alice : Person = {};
// ~~~~~ 'Person'유형에 필요한 'name' 속성이 '{}' 유형에 없습니다.

const bob = {} as Person; // 오류 없음

타입 선언은 할당되는 값이 해당 인터페이스를 만족하는지 검사한다. 앞의 예제에서는
그러지 못했기 때문에 타입스크립트가 오류를 표시했다. 타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하는 것이다.
타입 선언과 단언의 차이는 속성을 추가할 때도 마찬가지다.

const alice : Person = {
    name : 'Alice',
    occupation : 'TypeScript developer'
    // ~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있으며
    // 'Person' 형식에 'occupation'이(가) 없습니다.
};
const bob = {
    name : 'Bob',
    occupation : 'JavaScript developer'
} as Person; // 오류 없음

타입 선언문에서는 잉여 속성 체크(아이템 11)가 동작했지만, 단언문에서는 적용되지 않는다.
타입 단언이 꼭 필요한 경우가 아니라면, 안전성 체크도 되는 타입 선언을 권장한다.
화살표 함수의 타입 선언은 추론된 타입이 모호할 때가 있다. 예를 들어
다음 코드에서 Person 인터페이스를 사용하고 싶다고 가정을 해보자

const people = ['alice', 'bob', 'jan'].map(name => ({name}));
// Person[]을 원했지만 결과는 {name : string;}[]...

{name}에 타입 단언을 쓰면 문제가 해결되는 것처럼 보인다.

const people = ['alice', 'bob', 'jan'].map(name => ({name} as Person));
// 타입은 Person[]

그러나 타입 단언을 사용하면 앞선 예제들처럼 런타임에 문제가 발생하게 된다.
예를 들어보겠다.

const people = ['alice', 'bob', 'jan'].map(name => ({} as Person));
// 오류없음

단언문을 쓰지 않고 다음과 같이 화살표 함수 안에서 타입과 함께 변수를 선언하는 것이 가장 직관적이다.

const people = ['alice', 'bob', 'jan'].map(name => (
    const person:Person = {name};
    return person
)); // 타입은 Person[]

그러나 원래 코드에 비해 꽤나 번잡하게 보인다. 코드를 좀 더 간결하게 보이기 위해
변수 대신 화살표 함수의 반환 타입을 선언해보겠다.

const people = ['alice', 'bob', 'jan'].map(
    (name):Person => ({name})
); // 타입은 Person[]'

이 코드는 바로 앞의 번잡한 버전과 동일한 체크를 수행한다.
여기서 소괄호는 매우 중요한 의미를 지닌다. (name):Person은 name의 타입이 없고, 반환 타입이 Person이라고 명시한다. 그러나 (name : Person)은 name의 타입이 Person임을 명시하고 반환 타입이 없기 때문에 오류가 발생한다.
다음 코드는 최종적으로 원하는 타입을 직접 명시하고, 타입스크립트가 할당문의 유효성을 검사하게 한다.

그러나 함수 호출 체이닝이 연속되는 곳에서는 체이닝 시작에서부터 명명된 타입을 가져야한다. 그래야 정확한 곳에 오류가 표시된다.
다음으로 타입 단언이 꼭 필요한 경우를 살펴보겠다. 타입 단언은 타입체커가 추론한
타입보다 개발자 스스로가 판단하는 타입이 더 정확할 때 의미가 있다.
예를 들어, DOM 엘리먼트에 대해서는 타입스크립트보다 개발자가 더 정확히 알고있다.
타입스크립트는 DOM에 접근할 수 없다. 우리는 타입스크립트가 알지 못하는 정보를 가지고 있기 때문에 여기서는 타입 단언문을 쓰는 것이 타당하다.
또한 자주 쓰이는 특별한 문법(!)을 사용해서 null이 아님을 단언하는 경우도 있다.

const elNull = document.getElementById('foo'); //타입은 HTMLElement | null
const el = document.getElementById('foo'); //타입은 HTMLElement 

변수의 접두사로 쓰인 !는 불린의 부정문이다. 그러나 접미사로 쓰인 !는 그 값이
null이 아니라는 단언문으로 해석된다. 우리는 !를 일반적인 단언문처럼 생각해야한다.
단언문은 컴파일 과정 중에 제거되므로, 타입 체커는 알지 못하지만 그 값이
null이 아니라고 확실할 수 있을 때 사용해야한다. 만약 그렇지 않다면
null인 경우를 체크하는 조건문을 사용해야한다.
타입 단언문으로 임의의 타입 간에 변환을 할 수는 없다. A가 B의 부분집합인 경우에
타입 단언문을 사용해 변환할 수 있다. HTMLElement는 HTMLElement | null의
서브타입이기 때문에 이러한 타입 단언은 동작한다.

요약

  • 타입 단언(as Type)보다 타입 선언(:Type)을 사용해야한다.
  • 화살표 함수의 반환 타입을 명시하는 방법을 터득해야한다.
  • 타입스크립트보다 타입 정보를 더 잘 알고 있는 상황에서는 타입 단언문과 null 아님 단언문을 사용하면 된다.

참고 및 출처 : 이펙티브 타입스크립트 - 댄 밴더캄

profile
핫바리임

0개의 댓글