
런타임 이전의 컴파일 단계(코드를 입력하는 동안)에서 대부분의 에러를 확인할 수 있다.
런타임은 실제 고객이 마주할 수 있는 환경으로, 고객이 사이트를 이용하는 중 에러가 발생하는 단계라서 에러 발생 시 처리 비용이 크다.
타입을 강제하는 것은 예측 가능한 코드와 디버깅이 쉽게 만든다.
강력한 자동 완성 기능을 통해 생산성을 높인다.
인터페이스, 제네릭 등 객체 지향적 기능을 제공하여 대규모 프로젝트에 적합하다.
타입스크립트의 기본 원리는 구조적 타입 시스템(Structural type system, 일명 '덕 타이핑')
예: dictionary 또는 map이라고 불리는 구조는 안에 key, value가 몇 쌍이 있던 똑같은 모양으로 본다.
특정 타입이 아니어도 특정 타입의 '모양'을 갖고 있으면 그대로 타입 체크를 통과하는 것
$ tsc sample.ts
# compiled to `sample.js`
tsconfig.json 파일로 컴파일러 옵션을 관리할 수 있다.
{
"compilerOptions": {
"strict": true,
"target": "ES6",
"lib": ["ES2015", "DOM"],
"module": "CommonJS"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}




기본 타입 외에 다음 타입들이 있다.
정해진 타입으로 이루어진 고정된 길이(length)의 배열을 표현하는 타입
자리도 정해지고 갯수도 정해진다.
let tuple: [string, number];
tuple = ['a', 1];
tuple = ['a', 1, 2]; // Error - TS2322
tuple = [1, 'a']; // Error - TS2322
// 개별 변수
let userId: number = 1234;
let userName: string = 'HEROPY';
let isValid: boolean = true;
// 단일 튜플로 모아서 선언
let user: [number, string, boolean] = [1234, 'HEROPY', true];
console.log(user[0]); // 1234
console.log(user[1]); // 'HEROPY'
console.log(user[2]); // true
let users: [number, string, boolean][]; // 2차원 배열로 튜플 선언
users = [[1, 'Neo', true], [2, 'Evan', false], [3, 'Lewis', true]];
let tuple: [1, number];
tuple = [1, 2];
tuple = [1, 3];
tuple = [2, 3]; // Error - TS2322: Type '2' is not assignable to type '1'.
.push()나 .splice() 등으로 더 많은 값을 추가할 수 있다.let tuple: [string, number];
tuple = ['a', 1];
tuple = ['b', 2];
tuple.push(3);
console.log(tuple); // ['b', 2, 3] (가능)
tuple.push(true); // (불가능) Error - TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
let a: readonly [string, number] = ['Hello', 123];
a[0] = 'World'; // Error - TS2540: Cannot assign to '0' because it is a read-only property.
enum Week {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}
console.log(Week.Sun); // 0
console.log(Week['Sun']); // 0
console.log(Week[0]); // 'Sun'
enum Color {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
console.log(Color.Red); // red
console.log(Color['Green']); // green
"noImplicitAny": true로 설정하면 any를 사용할 때 에러가 발생한다.JSON.parse 메서드(리턴 타입을 추론할 수 없어서 any 사용)let a: any = 123;
let u: unknown = 123;
let v1: boolean = a; // any 타입은 어디든 할당 가능
let v2: number = u; // Error: unknown 값은 any 값을 제외한 다른 곳에는 할당 불가
let v3: any = u; // OK
let v4: number = u as number; // OK: 타입 단언을 사용하면 다른 타입에도 unknown 값을 할당 가능
type Result = {
success: true,
value: unknown
// Union으로 타입 합성 (type aliases)
} | {
success: false,
error: Error // 타입스크립트에서 new Error의 타입은 Error
}
export default function getItems(user: IUser): Result {
if (id.isValid) {
return {
success: true,
value: ['Apple', 'Banana'] // unknown 자리에 배열 할당
};
} else {
return {
success: false,
error: new Error('Invalid user.')
}
}
}
strictNullChecks: true를 설정하면 null을 포함하지 않는다)let obj: object = {};
let arr: object = [];
let func: object = function () {}; // 함수 포함
let nullValue: object = null; // null 포함
let date: object = new Date(); // 날짜 객체 포함
타입스크립트는 배열을 참조할 때 컴파일 단계에서 에러를 모를 수 있다.
const l = [1, 2, 3]
const item = l[3]
item + 1 // error이지만 컴파일 때는 모름
const user: { [key: string]: string } = { name: 'kyc' }
user.age + 1 // 마찬가지로 error이지만 컴파일 때 모른다
이를 체크하는 방법은 컴파일러 옵션의 noUncheckedIndexedAccess (But 완벽하지 않음)
const l = [1, 2, 3]
const item1 = l[3]
const item2 = l[2]
item1 + 1 // Object is possibly 'undefined'.(2532)
item2 + 1 // 근데 문제는 이거까지 에러가 난다는 거다
l.map((item) => item + 1) // map으로 돌려서 하는 연산에서는 괜찮음
noUncheckedIndexedAccess는 이처럼 적당히 경고를 날려주는 장점도 있지만 그다지 똑똑하지 않다는 점도 있다.
결론적으로 객체나 리스트의 값을 인덱싱(참조)할 때는 undefined가 있을 가능성에 대해 염두해 두어야 한다.
let num: number = undefined;
let str: string = null;
let obj: { a: 1, b: false } = undefined;
let arr: any[] = null;
let und: undefined = null;
let nul: null = undefined;
let voi: void = null;
strictNullChecks: true을 설정하면 null과 undefined 서로의 타입까지 엄격하게 할당할 수 없게 할 수 있다.let voi: void = undefined; // Ok
function hello(msg: string): void {
console.log(`Hello ${msg}`);
}
const hi: void = hello('world'); // Hello world
console.log(hi); // undefined
function error(message: string): never {
throw new Error(message);
}
const never: [] = []; // 타입이 빈 배열일 수는 없다
never.push(3); // Error - TS2345: Argument of type '3' is not assignable to parameter of type 'never'.
// 초기화된 변수 `num`
let num = 12;
// 기본값이 설정된 매개 변수 `b`
function add(a: number, b: number = 2): number {
// 함수의 반환 값(`a + b`)
return a + b;
}
val은 string이나 number, isNumber는 boolean이다.
개발자는 isNumber가 앞에 'is'가 붙은 네이밍을 통해 숫자 여부를 판단하는 값임을 알 수 있다.
타입스크립트는 ‘isNumber’라는 이름만으로는 이 내용을 추론할 수 없다.
function someFunc(val: string | number, isNumber: boolean) {
if (isNumber) {
// toFixed 메서드: 소수 자릿수로 반올림 후 string으로 변환하는 메서드 - 앞에 number만 가능
val.toFixed(2); // Error - TS2339: ... Property 'toFixed' does not exist on type 'string'.
// val이 string인지 number인지 모르는 타입스크립트는 toFixed를 객체의 프로퍼티로 추론해버림
}
}
타입 단언으로 이 문제를 해결할 수 있다.
function someFunc(val: string | number, isNumber: boolean) {
if (isNumber) {
(val as number).toFixed(2); // as로 단언 후 괄호로 묶어줌
}
}
const response = await fetch('/api/user')
const result = (await response.json()) as UserInterface
function isUser(data: unknown): data is UserInterface {
return data && typeof data === 'objet' && 'name' in data // ...
}
const response = await fetch('/api/user')
const result = await response.json()
if (!isUser(result)) {
throw new Error(`${result}는 UserInterface가 아님`)
}
타입 단언은 마치 프로그래머가 타입스크립트에게 “나는 알고 있으니까 나를 믿어!”라고 알려주는 것과 같다.
매개 변수 x가 null이나 undefined일 수 있어서 에러가 발생하는 경우
function fnA(x: number | null | undefined) {
return x.toFixed(2); // Error - TS2533: Object is possibly 'null' or 'undefined'
}
if 조건문이나 타입 단언으로 해결할 수도 있지만,
마지막처럼 Non-null 단언 연산자를 이용하여 간단히 처리할 수 있다.
// if 조건문
function fnD(x: number | null | undefined) {
if (x) { // true이면 = 존재하면 = null이나 undefined가 아니면
return x.toFixed(2);
}
}
// 타입 단언
function fnB(x: number | null | undefined) {
return (x as number).toFixed(2);
}
// Non-null 단언 연산자
function fnE(x: number | null | undefined) {
return x!.toFixed(2); // 위의 내용을 간단하게 표현 가능
}
val의 타입을 매번 보장하기 위해 타입 단언을 여러 번 사용한 경우
function someFunc(val: string | number, isNumber: boolean) {
if (isNumber) {
(val as number).toFixed(2);
isNaN(val as number);
} else {
(val as string).split('');
(val as string).toUpperCase();
(val as string).length;
}
}
타입 가드를 사용하면 타입스크립트가 추론 가능한 타입의 특정 범위를 보장할 수 있다.
반환 값으로 NAME is TYPE 라는 술부를 가진 함수를 생성 후 적용
// val이 number일 경우 val의 타입을 number로 확정해주는 함수를 따로 선언
function isNumber(val: string | number): val is number {
return typeof val === 'number'; // typeof 사용
}
function someFunc(val: string | number) {
if (isNumber(val)) { // isNumber 함수 적용
val.toFixed(2);
isNaN(val);
} else {
val.split('');
val.toUpperCase();
val.length;
}
}
함수를 따로 만들지 않고 간편하게 처리하기
typeof 키워드 (number, string, boolean, symbol만 타입 가드로 인식)in 연산자 (우변 객체(val)가 any 타입이어야)instanceof 연산자// typeof
function someFuncTypeof(val: string | number) {
if (typeof val === 'number') {
val.toFixed(2);
isNaN(val);
} else {
val.split('');
val.toUpperCase();
val.length;
}
}
// in
function someFuncIn(val: any) {
if ('toFixed' in val) { // toFixed가 val에 있으면 val이 number라는 뜻
val.toFixed(2);
isNaN(val);
} else if ('split' in val) { // split이 val에 있으면 val이 string이라는 뜻
val.split('');
val.toUpperCase();
val.length;
}
}
// instanceof
class Cat {
meow() {}
}
class Dog {
woof() {}
}
function sounds(animal: Cat | Dog) {
if (animal instanceof Cat) {
animal.meow();
} else {
animal.woof();
}
}
개인적인 생각으로는 typeof나 in을 쓰는 게 간단할 것 같다.
기존에 만들어진 인터페이스에 내용 추가
interface IAnimal {
name: string
}
interface ICat extends IAnimal {
meow(): () => string
}
const catInfo: ICat = {
name: 'Kitty',
meow(): () => 'Meowwww'
}
interface IFullName {
firstName: string,
lastName: string
}
interface IFullName { // 같은 이름으로 추가 타입을 선언하면 확장됨
middleName: string
}
const fullName: IFullName = {
firstName: 'Tomas',
middleName: 'Sean',
lastName: 'Connery'
};
인터페이스로 class의 타입을 정의하는 경우 implements 키워드를 사용
interface IUser {
name: string,
getName(): string
}
class User implements IUser {
constructor(public name: string) {}
getName() {
return this.name;
}
}
const neo = new User('Neo');
neo.getName(); // Neo
type 키워드와 union 연산자로 둘 이상의 타입을 조합할 수 있다.type TUser = {
name: string,
age: number,
isValid: boolean
} | [string, number, boolean];
let userA: TUser = {
name: 'Neo',
age: 85,
isValid: true
};
let userB: TUser = ['Evan', 36, false];
두 가지 값을 인수로 받는 toArray 함수에서 number 타입 선언이 여러 번 이루어지고 있다.
여기서 string 타입을 넣으면 에러가 발생한다.
function toArray(a: number, b: number): number[] {
return [a, b];
}
toArray(1, 2);
toArray('1', '2'); // Error - TS2345: Argument of type '"1"' is not assignable to parameter of type 'number'.
string도 허용되도록 union을 이용해 인수 선언을 확장했다.
가독성이 떨어지고, 세 번째 호출의 경우 의도치 않게 number와 string 타입을 동시에 받게 되었다.
function toArray(a: number | string, b: number | string): (number | string)[] {
return [a, b];
}
toArray(1, 2); // Only Number
toArray('1', '2'); // Only String
toArray(1, '2'); // Number & String
함수명 옆에 제네릭 타입 <T>를 작성하고 각 인수와 반환 값의 타입에도 T를 할당했다.
T는 타입 변수(Type variable)로 개발자가 제공한 타입으로 변환된다.
기본적으로 타입 추론이 되지만, 세 번째처럼 인수에 서로 다른 타입이 들어올 경우에는 union 타입으로 각각 타입을 명시해줘야 한다.
function toArray<T>(a: T, b: T): T[] {
return [a, b];
}
toArray(1, 2);
toArray('1', '2');
toArray<string | number>(1, '2'); // union으로 타입 명시
interface ICountries {
KR: '대한민국',
US: '미국',
CP: '중국'
}
let country: keyof ICountries; // 'KR' | 'US' | 'CP'
country = 'KR'; // ok
country = 'RU'; // Error - TS2322: Type '"RU"' is not assignable to type '"KR" | "US" | "CP"'.
let country: ICountries[keyof ICountries]; // ICountries['KR' | 'US' | 'CP'] = '대한민국' | '미국' | '중국'
country = '대한민국';
country = '러시아'; // Error - TS2322: Type '"러시아"' is not assignable to type '"대한민국" | "미국" | "중국"'.
T는 타입(Type), U는 또 다른 타입, K는 key를 의미하는 약어
Partial<T>Required<T>Readonly<T>Record<K, T>Pick<T, K>Omit<T, K>interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, 'description'>;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
createdAt: 1615544252770,
};
Exclude<T, U>type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // a, b, c 중에 a 제거
type T0 = 'b' | 'c';
Extract<T, U>null과 undefined만 제외하고 반환NonNullable<T>Parameters<T>ConstructorParameters<T>ReturnType<T>InstanceType<T>ThisParameterType<T>OmitThisParameter<T>ThisType<T>한눈에 보는 타입스크립트(updated)
[Typescript] 기존 React + js 프로젝트 ts로 바꾸기
타입스크립트에서 조심해야 할 습관
A difference between TypeScript Omit and exclude