타입스크립트에서는 타입 표기가 없는 경우, 타입 정보를 제공하기 위해서 타입을 유추한다.
let a = 5;
a = "hello"; // => error
let a = 5;
a = 40; // => 정상 동작
타입스크립트가 타입 추론을 통해 판단할 수 있는 타입의 범주를 넘는 경우, 더 이상 추론하지 않도록 지시할 수 있다.
이를 ‘타입 단언’이라고 하며, 이는 프로그래머가 타입스크립트보다 타입에 대해 더 잘 이해하고 있는 상황을 의미한다.
타입스크립트는 ‘isNumber’라는 이름만으로 위 내용을 추론할 수 없기 때문에 “val이 문자열인 경우 toFixed를 사용할 수 없다”고 (컴파일 단계에서) 다음과 같은 에러를 반환한다.
function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
val.toFixed(2); // Error - TS2339: ... Property 'toFixed' does not exist on type 'string'.
}
}
따라서 우리는 isNumber가 true일 때 val이 숫자임을 다음과 같이 단언할 수 있다.
function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
// 1. 변수 as 타입
(val as number).toFixed(2);
// Or
// 2. <타입>변수
// (<number>val).toFixed(2);
}
}
let bool01: boolean = true;
let bool02: boolean = false;
let num01: number = 5;
let num02: number = 3.14;
let num03: number = NaN;
ES6의 템플릿 문자열도 지원한다.
let str01: string = 'text';
let str02: string = `my name is ${val}`;
const person: {
name: string;
age: number;
} = {
name: 'max',
age: 30,
};
console.log(person.name);
물론 객체 타입은 중첩 객체에 대해서도 생성할 수 있다.
const product = {
id: 'abc1',
price: 12.99,
tags: ['great-offer', 'hot-and-new'],
details: {
title: 'Red Carpet',
description: 'A great carpet - almost brand-new!'
}
}
//이러한 객체의 타입은 아래와 같다.
{
id: string;
price: number;
tags: string[];
details: {
title: string;
description: string;
}
}
따라서 객체 타입 안에 객체 타입이 있다고 말할 수 있다.
let favoritesports: string[];
favoritesports = ['sports', 'running'];
let favoritesports2: any[];
favoritesports2 = ['sports', 1, 3, true, { name: 'ye' }];
let favoritesports3: (string | number)[];
favoritesports3 = ['sports', 1, 3];
const person2 = {
name: 'max',
age: 30,
hobbies: ['sports', 'cooking', 3],
};
for (const hobby of person2.hobbies) {
console.log(hobby.toUpperCase());
//=> 타입 추론으로 number 타입이 있기 때문에 toUpperCase error가 발생
}
배열 안의 요소가 원하는 길이만큼 원하는 타입을 지정할 수 있다.
const person1: {
hobbies: string[];
role: [number, string]; // tuple : 배열 안의 요소가 원하는 길이만큼 원하는 타입을 지정
} = {
hobbies: ['sports', 'cooking'],
role: [10, 'tuple'],
};
person1.role.push('admin'); // => 정상
person1.role[1] = 10; // => Error
person1.role = [0, 'dd', 'dd']; // => Error
let tuple: [string, number];
tuple = ['hello', 10]; // OK
tuple.push(true); // Error
특정 값들의 집합을 의미하는 자료형
문자형 이넘과 숫자형 이넘을 지원
enum Role {ADMIN = 'admin', READ = 12, AUTHOR}
const person = {
name: 'max',
age: 30,
hobbies: ['sports', 'cooking'],
role: Role.ADMIN,
};
if (person.role === Role.ADMIN) {
console.log(Role.ADMIN);
}
enum Color1 {Red, Green, Blue};
let c1: Color1 = Color1.Green;
console.log(c1); // 1
type Product = {title: string; price: number;}
const p1: Product = { title: 'A Book', price: 12.99, isListed: true }
// isListed는 Product 타입에 포함되지 않아 컴파일 에러가 발생한다.
//=> ERROR
모든 타입의 value를 허용한다.
타입 추론(type inference)할 수 없거나 타입 체크가 필요 없는 변수에 사용한다.
하지만 any를 쓸 경우 Typescript 컴파일러가 아무것도 점검할 수 없기 때문에, 타입스크립트를 쓰는 장점이 사라지게 된다.
let favoritesports2: any[];
favoritesports2 = ['sports', 1, 3, true, { name: 'ye' }];
let favoritesports22: any;
favoritesports22 = { name: 'ye' };
자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입
function combine(input1: number | string, input2: number | string) {
//타입에 따른 런타임 확인이 필요한 경우 다음과 같은 조건 분기를 할 수 있다.
let result;
if (typeof input1 === 'number' && typeof input2 === 'number') {
result = input1 + input2;
} else {
result = input1.toString() + input2.toString();
}
return result;
}
const combineAges = combine(30, 26);
console.log(combineAges);
const combineNames = combine('max', 'anna');
console.log(combineAges);
문자열과 숫자, boolean 세 가지 리터럴 타입이 있는데 이를 사용하면 문자열이나 숫자에 정확한 값을 지정할 수 있다.
function combine2(
input1: number | string,
input2: number | string,
resultConversion: 'as-number' | 'as-text'
) {
let result;
if (
(typeof input1 === 'number' && typeof input2 === 'number') ||
resultConversion === 'as-number'
) {
result = +input1 + +input2;
} else {
result = input1.toString() + input2.toString();
}
return result.toString();
}
const combineNames = combine2('max', 'anna', 'as-text');
console.log(combineNames); // => maxanna
const combineStingAges = combine2('30', '26', 'as-number');
console.log(combineStingAges); //=> 56
특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미
type MyName = string;
const name: MyName = 'capt';
type Combinable = number | string;
type ConversionDescriptor = 'as-number' | 'as-text';
function combine3(
input1: Combinable,
input2: Combinable,
resultConversion: ConversionDescriptor
){ ... }
타입 별칭을 사용하여 타입을 직접 “생성”할 수 있다. 유니온 타입을 저장하는 것만 가능한 것이 아니다. 복잡할 수 있는 객체 타입에도 별칭을 붙일 수 있다.
type User = { name: string; age: number };
const u1: User = { name: 'Max', age: 30 }; // this works!
//TODO return 타입 설정
function add(n1: number, n2: number): number {
return n1 + n2;
}
//TODO return 타입 : void / 아무런 return이 없을 때
// functin에서 아무런 return이 없을 때 undefied 타입이 불가능하다
// undefined 대신 void를 사용한다
function printResult(num: number): void {
console.log('result' + num);
}
function printResult2(num: number): void {
console.log('result' + num);
return;
}
// undefined를 return할 때 undefined 타입을 사용할 수는 있지만,
// 거의 사용하지 않는다.
function printResult3(num: number): undefined {
console.log('result' + num);
return;
}
printResult(add(5, 12));
// undefined 타입은 존재한다
let someValue: undefined;
//TODO 함수 타입
let combineValues: Function;
//TODO 두개의 number 타입 매개변수를 받아 number 타입을 반환한다
let combineValues2: (a: number, b: number) => number;
console.log(combineValues(8, 8));
콜백 함수는 자신이 전달되는 인수가 반환 값을 기대하지 않는 경우(void 타입인 경우)에도 값을 반환할 수 있다.
//TODO 함수 타입 및 콜백
function addAndHandle(n1: number, n2: number, cb: (num: number) => number) {
const result = n1 + n2;
cb(result);
}
addAndHandle(10, 20, (result) => {
console.log(result);
return 3;
});
사용자가 어떤 값을 입력할지 모를 경우 모든 타입을 사용할 수 있다.
unknown
: any 타입을 제외한 다른 타입으로 선언한 변수에 할당할 수 없다
: if문으로 타입을 설정한다면 error가 발생하지 않고 할당이 가능하다.
: 때문에 타입을 특정할 수 없지만, 특정 타입 조건에 대해서 확인이 필요하다면 any보다는 unknown 타입을 사용하는 것이 좋다.
any
: 모든 타입으로 선언한 변수에 할당할 수 있다.
//TODO numknown 타입
let userInput: unknown;
let userInput2: any;
let userInput3: string;
userInput = 5;
userInput = 'max';
// userInput3 = userInput; //=> error
userInput2 = userInput; // any 타입에는 할당할 수 있다.
userInput3 = userInput2; //=> 정상
userInput2 = userInput3; //=> 정상
// if문으로 타입을 설정한다면 error가 발생하지 않고 할당이 가능하다.
// 때문에 타입을 특정할 수 없지만, 특정 타입 조건에 대해서 확인이 필요하다면 any보다는 unknown 타입을 사용하는 것이 좋다.
if (typeof userInput === 'string') {
userInput3 = userInput;
}
never는 절대 발생하지 않는 값의 타입을 표현하는데 사용하며 보통 에러를 발생하거나, 절대 return하지 않는(무한 루프) 함수의 return type으로 많이 사용한다.
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
function fail() { // return type이 never으로 추론됨
return error("Something failed");
}
never 타입은 모든 타입의 서브 타입이며, 어떤 타입도 ‘never’에 assign되지 않는다.
let any: any;
let bool: boolean = true;
let nullVal: null = null;
let undefinedVal: undefined = undefined;
let never1: never = any; // Type 'any' is not assignable to type 'never'.
let never4: never = nullVal; // Type 'null' is not assignable to type 'never'.
let never5: never = undefinedVal; // Type 'undefined' is not assignable to type 'never'
never는 함수가 종료하지 않아 결코 return하지 않을 때 사용된다. 무한루프 혹은 Error 메시지를 throw할 때 return type이 never이다.
void는 return 값이 없을 뿐이지 함수는 종료한다.