본문 바로가기
백엔드 개발

#026. TS 의 타입 넓히기

by iamjoy 2023. 2. 7.

개념

타입스크립트가 타입을 추론할 때 사용하는 방법으로, 너무 광범위 하지 않으면서도 일단 추론된 타입이 최대한 바뀌지 않는 방향으로 추론된다. 타입 넓히기를 배우면 타입을 따로 선언하지 않은 경우 타입스크립가 어떤 식으로 타입을 추론하는 지 예측할 수 있고 적절하게 제어할 수 있다.

모든 코드는 깃헙에서 직접 실행해볼 수 있다 🐱 (https://github.com/erie0210/effective-typescript/tree/main)

적용

예시1: 아래와 같은 예시를 실행해보면 x 타입을 제대로 추론하지 못해서 에러가 발생한다.

interface Vector3 {
    x: number;
    y: number;
    z: number;
}

function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z'){
    return vector[axis];
}

let x = 'x';
let vec = { x:10, y:20, z:30 };

getComponent(vec, x); // <----------- 에러 발생:
//                ~~ Argument of type 'string' is not assignable to parameter of type '"x" | "y" | "z"'

언뜻 보면 axis 타입이 'x' | 'y' | 'z' 이기 때문에 'x' 를 할당받은 x를 넣어주면 될 것 같지만 실제 ts에서는 아래와 같은 에러가 발생한다. 설명을 읽어보면 string 타입인 x를  'x' | 'y' | 'z'  에 할당할 수 없다는 메시지이다.

Argument of type 'string' is not assignable to parameter of type '"x" | "y" | "z"'

'x'가 왜 stirng로 추론되었는 지 이해하려면 '타입 넓히기'를 이해해야한다.

런타임에서는 모든 타입이 유일하다. let c = '1' 이라는 코드가 불러와져서 사용되는 시점에는 무조건 고정된 하나의 값을 가진다는 의미이다. TS가 작동하는 빌드 타임 시점에서는 타입을 추론해야한다. 예를 들어서 아래와 같이 작성된 코드가 있다면 나올 수 있는 여러 타입이 있는데, 이 중에서 가장 런타임 시점에 바뀌지 않을 타입으로 선언하려고 한다. (이를 명확성과 유연성 사이 균형이라고 한다.) 

const mixed = ['x', 1]; 

// 예상 가능한 추론 타입
// ('x'|1)[] ---> 'x'나 1을 가지는 배열 
// [string, number] ---> 튜플
// (string|number)[] -------------------> 실제 TS 가 추론하는 타입
// [any, any]
// any[]

 

코드 작성 또는 빌드 타임 시점에 보다 정확하게 타입을 제어하기 위해서 아래와 같은 방법을 사용할 수 있다. 

방법1: let 이 아닌 const를 사용하는 것이다.  const 를 사용하면 재할당 할 수 없으므로 TS는 더 좁은 타입으로 추론한다.
아래의 예시는 이 글의 가장 첫번째 예시에 이어진다. 하지만 이번엔 const를 이용해 선언했기 때문에 변수 y는 항상 'y' 타입을 가지고 (string이 아닌) 이 때문에 'x' | 'y' | 'z' 유니언 타입의 부분 집합이 될 수 있다.

// 타입 넓히기 제어 방법
// 방법 1 : const 사용

const y = 'y'
let vec2 = { x:10, y:20, z:30}
getComponent(vec, y)

 

다만 const를 사용할 때에도 예외가 있다. 객체 {} , 튜플 [], 배열 []이다.
아래와 같이 const로 선언되었다고 할지라도 x에 어떤 값이든 할당된다. Call By Reference 때문인데, 배열을 예로 들면 배열 그 자체는 const로 선언되지만 배열 안의 개별 값들은 주소값을 가지고 있기 때문에 실제 주소값에 들어가는 값들은 바뀔 수 있는 것이다.

// 예외: 객체, 배열, 튜플
const v = {
    x: 1
}

v.x = 3
v.x = '3'
v.y = 4

 

이런 예외를 해결하는 방법으로는 아래와 같이 두 가지가 있다.
(1) 명시적으로 타입을 선언하기 (2) const 단언하기

// 예외에 대한 제어 방법 1: 타입선언
const v2: { x:1|3|5 } = {
    x:1
}

// 예외에 대한 제어 방법 2: const 이용
const v3 = {
    x:1 as const,
    y: 3
}

 

위의 예외적인 케이스를 주의하면서 const를 사용하면 보다 정확하게 TS 타입 추론을 사용할 수 있다.