const x: number = 12; // 타입 구문 불필요
const x = 12; // 타입이 number로 추론됨. (x: number)
const person = {
name: "Smith",
born: {
where: "New York",
when: "Nov. 26, 1883",
},
};
/*
person: {
name: string;
born: {
where: string;
when: string;
}
}
*/
interface Product {
id: string;
name: string;
price: number;
}
function logProduct(product: Product){
const { id, name, price } = product; // id: string, name: string, price: number
console.log(id, name, price);
}
function square(nums: number[]){
return nums.map(x => x * x);
}
const squares = sqaure([1, 2, 3, 4]); // squares: number[]
function parseNumber(str: string, base = 10){
// ...
}
Example 1
interface Vector3 {
x: number;
y: number;
z: number;
}
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z'){
return vector[axis];
}
let x = 'x'; // x: string
let vec = {x: 10, y: 20, z: 30}; // { x: number, y: number, z: number }
getComponent(vec, x);
// error (string 형식의 인수는 'x' | 'y' | 'z' 형식의 매개변수에 할당될 수 없습니다.)
x
의 타입은 할당 시점에서 넓히기가 동작하여 타입이 string
으로 추론되었다.string
타입은 'x' | 'y' | 'z'
타입에 할당이 불가능하기 때문에 오류가 발생한다.let
대신 const
로 변수를 선언하면 더 좁은 타입으로 추론된다.const x = 'x'; // x: 'x'
let vec = {x: 10, y: 20, z: 30}; // { x: number, y: number, z: number }
getComponent(vec, x); // ok
let
으로 선언된 변수는 재할당이 가능하지만, const
으로 선언된 변수는 재할당이 불가능하므로 더 좁은 타입으로 추론될 수 있고, 넓히기 과정을 제어할 수 있다.Example 2
const mixed = ['x', 1];
mixed
배열의 타입이 될 수 있는 후보들은 다음과 같다.
- ('x' | 1 )[]
- ['x', 1 ]
- [string, number]
- readonly [string, number]
- (string | number)[]
- readonly (string | number)[]
- [any, any]
- any[]
mixed
배열의 초기화 값을 가지고 타입을 (string | number)[]
라고 추론한다.mixed
배열 타입이 (string | number)[]
으로 추론됨에 따라 각 배열의 원소의 타입도 string | number
로 추론된다.let x = 12; // x: number
x = mixed[1]; // error: string | number 타입은 number에 할당할 수 없다.
mixed[1]
의 타입은 number
이지만, 넓히기 과정을 통해 string | number
타입으로 추론되어 오류가 발생한다.mixed
배열의 각 원소가 좁은 타입으로 사용되기 위해서는 타입 선언이 필요하다.const mixed : [string, number] = ['x', 1];
let x = 12;
x = mixed[1]; // ok
const
단언문 (as const
)을 이용하면 배열의 원소가 최대한 좁은 타입으로 추론된다.const mixed = ['x', 1] as const; // mixed: readonly ["x", 1]
let x = 12;
x = mixed[1]; // ok
👉 예시를 통해 보았듯이 타입 넓히기 과정을 제어하는 방법으로는
const
로 변수 선언하기, 타입 선언,const
단언문이 있다.
가장 대표적인 타입 좁히기가 null 체크이다.
const el = document.getElementById('foo'); // el: HTMLElement | null
if (el){
// el: HTMLElement
el.innerHTML = "party";
}
else {
// el: null
alert("No element #foo");
}
위와 같이 조건문으로 타입을 좁힐 수 있지만, 실수를 저지르기 쉽다.
const el = document.getElementById('foo'); // el: HTMLElement | null
if (typeof el === "object"){ // 유니온 타입에서 null을 제외하려고 했지만, 잘못된 방법 사용
// el: HTMLElement | null
}
typeof null
이 object
이기 때문에 조건문에서 null
타입이 제외되지 않았다.function foo(x?: number|string|null){
if (!x){
// x: number | string | null | undefined
}
}
x
의 타입을 null | undefined
로 좁힐려고 했으나 빈 문자열과 0도 false가 되기 때문에, 타입이 좁혀지지 않았다.instanceof 연산자를 이용하여 타입 좁히기를 수행할 수 있다.
function contains(text: string, search: string|RegExp){
if (search instanceof RegExp){ // search: RegExp
return !!search.exec(text);
}
// search: string
return text.includes(search);
}
속성체크를 통해 타입을 좁힐 수 있다.
interface A {
a: number;
}
interface B {
b: number;
}
function pickAB(ab: A|B){
if ('a' in ab){
// ab: A
console.log(ab.a);
}
else {
// ab: B
console.log(ab.b);
}
// ab: A | B
}
Array.isArray
와 같은 일부 내장 함수로도 타입을 좁힐 수 있다.
function contains(text: string, terms: string | string[]){
const termList = Array.isArray(terms) ? terms : [terms];
// termList: string[]
}
타입을 좁히는 또 다른 방법은 명시적 태그를 붙이는 것이다.
interface UploadEvent {
type: 'upload';
filename: string;
contents: string;
}
interface DownloadEvent {
type: 'download';
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent; // 태그된 유니온 (tagged union)
function handleEvent(e: AppEvent) {
switch (e.type){
case 'download':
// e: DownloadEvent
case 'upload':
// e: UploadEvent
}
}
커스텀 함수를 이용한 사용자 정의 타입 가드를 통해 타입을 좁힐 수 있다.
// Example 1
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement){
if (isInputElement(el)){
// el: HTMLInputElement
return el.value;
}
// el: HTMLElement
return el.textContent;
}
// Example 2
const jackson5 = ["Jackie", "Tito", "Jermaine", "Marlon", "Michael"];
const members = ["Janet", "Michael"].map(who => jackson5.find(n => n == who));
// members: (string | undefined)[]
// filter 함수를 사용해 undefined 타입 제외 시도
const members = ["Janet", "Michael"].map(
who => jackson5.find(n => n == who)
).filter(who => who !== undefined); // members: (string | undefined)[]
// 타입 가드 사용
function isDefined<T>(x: T | undefined): x is T {
return x !== undefined;
}
const members = ["Janet", "Michael"].map(
who => jackson5.find(n => n == who)
).filter(isDefined); // members: string[]