TypeScript 3.4: const assertion

Kim Seungha
6 min readMay 12, 2019

--

TypeScript 3.4 버전에서 const assertion 이라는 기능이 추가되었습니다.

let, const 선언의 타입 추론

정적 타이핑을 해주는 언어에는 대개 ‘타입 추론’이라는 기능이 포함되어 있습니다. 선언하는 모든 변수마다 항상 그 타입까지 같이 적어주어야 한다면 여간 불편한 일이 아닐 것입니다. 타입 추론을 지원하는 언어에서는, 변수에 대입하는 ‘리터럴의 타입’을 보고 해당 변수의 타입을 자동으로 지정해줍니다. TypeScript 역시 타입 추론 기능을 잘 지원하고 있습니다.

// 굳이 이렇게 적어주지 않아도 됩니다.
let hello: string = 'world';
// 타입 추론 기능을 활용해서, 아래와 같이 짧게 적어줄 수 있습니다.
let hello = 'world';

여기에서, 저는 ‘world’ 라는 문자열을 hello 변수에 대입했습니다. TypeScript는 똑똑하게도 hello 변수가 string 타입이어야 한다는 사실을 알아차렸습니다.

let 변수에 대한 타입 추론 (문자열 리터럴)

위 예제에서 let 대신 const 변수로 선언하면, 아래와 같이 string 대신에 ‘world’ 타입으로 추론됩니다.

const 변수에 대한 타입 추론 (문자열 리터럴)

TypeScript는 특정 문자열 자체를 타입으로 다룰 수 있게 해주는 string literal type을 지원합니다. 즉, 위와 같은 타입 정보는 hello 변수는 반드시 “world” 문자열이어야 하며, 다른 문자열이 될 수 없다는 사실을 나타냅니다.

위에서 보신 바와 같이 변수를 let으로 선언하느냐, const로 선언하느냐에 따라 타입 추론의 규칙이 달라집니다. 이는 합리적인데, let 변수는 다른 값이 대입될 수 있고, const 변수에는 다른 값이 대입될 수 없기 때문입니다.

const assertion

TypeScript 3.4에 추가된 const assertion 기능을 사용하면, let 변수에 대해서도 const 변수를 사용할 때와 같은 타입 추론 규칙을 적용할 수 있습니다.

const assertion을 적용하려면, ‘const’ 라는 키워드로 타입 단언을 하면 됩니다.

let hello = 'world' as const; // ts, tsx 파일에서
let hello = <const>'world'; // ts 파일에서

타입 추론이 어떻게 되었는지 볼까요?

let 변수에 대한 const assertion

hello를 let 변수로 선언했음에도, 마치 const 변수로 선언한 것처럼 “world” 타입으로 추론되었습니다. 위의 경우, hello 변수에 “world” 이외의 다른 값을 대입하려고 하면 컴파일 타임 에러가 납니다.

const assertion에 의한 타입 에러

객체에 대한 const assertion

이렇게 별 쓸모 없어 보이는 기능이 왜 추가된 걸까요?

이번에는 const 변수에 객체를 대입해서, 타입 추론이 어떻게 되는지 보겠습니다.

const 변수에 대한 타입 추론 (객체 리터럴)

변수가 const로 선언되었다 할지라도, 객체 내부의 속성에 대한 타입은 ㄹ넓은 범위로 추론됩니다. 이 역시 생각해보면 당연한데, 변수가 const 일지라도 hello 속성의 값은 얼마든지 변경될 수 있기 때문입니다.

이런 경우, 타입 추론의 범위를 좁혀주기 위해 const assertion을 사용할 수 있습니다.

// 하나의 속성에 대한 const assertion
const obj = {
hello: 'world' as const,
foo: 'bar'
};
// 모든 속성에 대한 const assertion
const obj = {
hello: 'world',
foo: 'bar'
} as const;

타입 추론의 결과를 보면,

하나의 속성에 대한 const assertion
모든 속성에 대한 const assertion

const assertion + discriminated union

TypeScript에는 discriminated union이라는 기능이 있습니다. 이 기능을 통해 타입 추론의 범위를 좁혀줄 수 있습니다.

아래 예제를 생각해봅시다.

const circle = {
type: 'circle',
radius: 10
};
const square = {
type: 'square',
width: 10,
height: 20
};
type Shape = typeof circle | typeof square;function draw(shape: Shape) {
switch (shape.type) {
case 'circle':
console.log(shape.radius);
break;
case 'square':
console.log(shape.width);
break;
}
}

JavaScript에서라면 크게 문제가 되지 않을 코드입니다. 하지만 TypeScript에선 컴파일이 되지 않습니다.

타입이 호환되지 않음을 나타내는 에러

이런 에러가 나는 이유는, shape.type의 값이 ‘circle’ 인지 확인되었음에도 불구하고 shape 변수의 타입이 [typeof circle]로 좁혀지지 않았기 때문입니다. 실제로 Shape 타입이 무엇인지를 확인해보면,

객체 내부의 문자열 리터럴에 대한 타입 추론이 이루어지지 않음

[type: string]과 같이 문자열 리터럴의 타입이 제대로 추론되지 않은 것을 확인할 수 있습니다. 이 때,

  • 각 객체의 type 속성에 const assertion을 적용하거나,
  • 두 객체에 통째로 const assertion을 적용하면,

위에서 발생한 타입 에러가 해결됩니다. 이 링크에서 직접 시험해보세요.

다음 글에서는 위 성질을 이용해 Redux 액션의 타이핑을 좀 더 쉽게 할 수 있는 방법을 다루어보겠습니다.

--

--