TS 타입 시스템은 JS의 기능들을 제공하면서 그 위에 자체 레이어를 추가한다.
JS는 이미 string
, number
, object
, undefined
같은 원시타입을 가지고 있으나 전체 코드베이스에 일관성있게 할당되었는지는 미리 확인해 주지 않는다. TS는 이 레이어로서 동작한다. 잘 동작하는 JS코드는 TS코드이기도 하나 TS의 타입 검사자는 사용자가 생각한 일과 JS가 실제로 하는 일 사이의 불일치를 나타낼 수 있다.
TS는 보통 타입을 생성해준다.
let helloWorld = "Hello World";
// 변수 선언과 동시에 값을 할당하면 TS는 타입을 가지는 '타입 시스템'을 구축할 수 있다.
TS가 helloWorld
가 string
임을 알게 되는 방식이다.
JS는 다양한 디자인 패턴을 가능케하는 동적 언어이다. 몇몇 디자인 패턴은 자동으로 타입을 제공하기 힘들 수 있어 이러한 경우 TS는 JS언어 확장을 지원한다.
ex) name:string
과 id:number
를 포함하는 추론 타입을 가진 객체 생성 예제를 보자.
const user = {
name:"Hayes",
id: 0,
};
interface
로 선언한다.interface User {
name: string;
id: number;
}
: TypeName
의 구문으로 JS 객체가 새로운 interface
형태를 따르고 있음을 선언할 수 있다.interface User {
name: string;
id: number;
}
...
const user: User = {
name: "Hayes",
id: 0,
};
// @errors: 2322
interface User {
name: string;
id: number;
}
const user: User = {
username: "Hayes",
id: 0,
};
interface User {
name: string;
id: number;
}
class UserAccount {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
const user: User = new UserAccount("Murphy", 1);
interface User {
name: string;
id: number;
}
function getAdminUser(): User {
... }
function deleteUser(user: User) {
... }
JS에서 사용 가능한 원시 타입
boolean
,bigint
,null
,number
,string
,symbol
,object
,undefined
TS에서는 JS에서 사용 가능한 원시타입에다가 몇 가지를 더 제공함
any
: 무엇이든 허용함unknown
: 이 타입을 사용하는 사람이 타입을 선언했는지를 확인하기never
: 이 타입은 발생될 수 없음void
:undefined
을 리턴하거나, 리턴 값이 없는 함수일 때
타입 구축을 위한 2가지 구문이 있다.
interface
를 우선적으로 사용하고, 특정 기능이 필요할 때 type
을 사용해야 한다.
객체들을 조합해 더 크고 복잡한 객체를 만드는 방법처럼, TS에 타입으로 이를 수행하는 도구가 있다. 여러 타입을 이용해 새 타입을 작성하기 위해 일상적인 코드에서 가장 많이 사용되는 코드로는 '유니언(Union)'과 '제네릭(Generic)'이 있다.
유니언은 타입이 여러 타입 중 하나일 수 있음을 선언하는 방법이다.
예를 들어 boolean
타입을 true
or false
로 설명할 수 있다.
type MyBool = true | false;
에디터에서 위의 코드를 작성한 후, MyBool
위에 커서를 갖다대면 boolean
으로 분류되는 것을 볼 수 있다. (구조적 타입 시스템의 프로퍼티)
유니언 타입이 가장 많이 사용되는 예제 중 하나는 값이 다음처럼 허용되는 string
또는 number
의 리터럴 집합을 설명하는 것이다.
type WidnowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
유니언은 다양한 타입을 처리하는 방법을 제공한다. 예를 들어 array
or string
을 받는 함수가 있을 수 있다.
function getLength(obj: string | string[]) {
return obj.length;
}
TS는 코드가 시간에 따라 변수가 변경되는 방식을 이해하며, 검사를 통해 타입을 골라낼 수 있다.
예를 들어 typeof obj === "string"
을 이용하여 string
과 array
를 구분할 수 있고, TS는 객체가 다른 코드 경로에 있음을 알게 된다.
function wrapInArray(obj: string | string[]) {
if(typeof obj === "string") {
return [obj];
// ^?
} else {
return obj;
}
}
제네릭은 타입에 변수를 제공하는 방법 이다. 배열이 일반적인 예시이다.
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string };
제네릭을 사용하는 고유 타입을 선언할 수 있다.
// @errors: 2345
interface Backpack<Type> {
add: (obj: Type) => void;
get: () => Type;
}
// 이 줄은 TypeScript에 `backpack`이라는 상수가 있음을 알리는 지름길이며
// const backpack: Backpack<string>이 어디서 왔는지 걱정할 필요가 없습니다.
declare const backpack: Backpack<string>;
// 위에서 Backpack의 변수 부분으로 선언해서, object는 string입니다.
const object = backpack.get();
// backpack 변수가 string이므로, add 함수에 number를 전달할 수 없습니다.
backpack.add(23);
TS의 핵심 원칙 중 하나는 타입 검사가 값이 있는 형태에 집중한다는 것이다. 이를 '덕타이핑(duck typing)' 또는 '구조적 타이핑'이라고 부른다.
구조적 타입 시스템에서 두 객체가 같은 형태를 가지면 같은 것으로 간주된다.
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
const point = { x: 12, y: 26};
printPoint(point); //"12,26"을 출력한다.
point
변수는 Point
타입으로 선언된 적이 없지만 TS는 타입 검사에서 point
의 형태와 Point
의 형태를 비교한다. 둘 다 같은 형태이므로 통과한다.
형태 일치에는 일치시킬 객체 필드의 하위 집합만 필요하다.
// @errors: 2345
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// ---cut---
const point3 = { x: 12, y: 26, z: 89 };
printPoint(point3); // prints "12, 26"
const rect = { x: 33, y: 3, width: 30, height: 80 };
printPoint(rect); // prints "33, 3"
const color = { hex: "#187ABF" };
printPoint(color);
구조적으로 클래스와 객체가 형태를 따르는 방법에는 차이가 없다.
// @errors: 2345
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// ---cut---
class VirtualPoint {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const newVPoint = new VirtualPoint(13, 56);
printPoint(newVPoint); // prints "13, 56"
객체 or 클래스에 필요한 모든 속성이 존재한다면, TS는 구현 세부 정보에 관계없이 일치하게 본다.
cf. TypeScript 핸드북