원 글은 TypeScript for JavaScript Programmers이며, 이 글은 미세한 추가/삭제가 있습니다. 🤓
TypeScript는 JavaScript 언러를 알고 있으며 대부분의 경우 타입을 생성해준다.
예를 들면, 변수를 생성함과 동시에 특정 값에 할당하는 경우, TypeScript는 그 값을 해당 변수의 타입으로 사용한다.
let helloWorld = "Hello World!";
TypeScript는 JavaScript 코드를 받아들이면서 타입을 가지는 타입 시스템을 구축할 수 있다. 이는 코드에서 타입을 명시하기 위해 추가로 문자를 사용할 필요가 없는 타입 시스템을 제공한다.
위의 예제에서 TypeScript가 helloWorld
가 string
타입 임을 알게 되는 방식이다.
JavaScript는 다양한 디자인 패턴을 가능하게 하는 동적 언어이기 때문에 몇몇 디자인 패턴은 자동으로 타입을 제공하기 힘들 수 있다. 이러한 경우에 TypeScript는 타입이 무엇이 되어야 하는지 명시 가능한 JavaScript언어의 확장을 지원한다.
아래의 예제를 통해 추론 타입을 가진 객체를 생성해보자.
const user = {
name: "seob",
id: 1,
};
이 객체의 형태를 명시적으로 나타내기 위해서 interface
를 선언한다.
interface User {
name: string;
id: number;
}
이제 변수 선언 뒤에 : 타입이름
의 구문을 사용해 JavaScript 객체가 새로운 interface
의 형태를 따르고 있음을 선언할 수 있다.
interface User {
name: string;
id: number;
}
const user: User = {
name:"seob",
id: 1,
};
해당 인터페이스에 맞지 않는 객체를 생성하면 TypeScript는 경고를 띄운다.
interface User {
name: string;
id: number;
}
const user: User = {
userName:"seob",
id: 1,
};
interface
와 user
의 키 값이 서로 다르다.
Type '{ username: string; id: number; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'username' does not exist in type 'User'.
TypeScript는 JavaScript와 마찬가지로 객체 지향 프로그래밍을 지원하기 때문에 인터페이스는 클래스로도 선언할 수 있다.
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) {
//...
}
JavaScript에서 이미 사용할 수 있는 원시 타입이 있다.
boolean
bigint
null
number
string
symbol
object
undefined
위의 타입 모두 당연히 TypeScript에서 사용할 수 있고 추가로 사용할 수 있는 타입이 조금 더 있다.
any
: 어떤 타입이든 허용한다.unknown
: 이 타입을 사용하는 사람이 타입이 무엇인지 선언했는가를 확인해야한다.never
: 이 타입은 발생될 수 없다.void
: undefined
를 리턴하거나 리턴 값이 없는 함수이다.타입을 빌딩할 때 interface
, type
두 가지 구문이 있다.
특정 기능이 필요할 때만 type
을 사용하고 interface
를 우선적으로 사용하는 것이 좋다.
TypeScript: interface vs Types from Stack Overflow
객체들을 조합하여 더 크고 복잡한 객체를 만드는 방법과 유사하게 TypeScript에 타입으로 이를 수행하는 도구가 있다. 여러가지 타입을 이용하여 새 타입을 작성하기 위해 일상적인 코드에서 가장 많이 사용되는 두 가지 코드로는 Union과 Generic이 있다.
유니언은 타입이 여러 타입 중 하나일 수 있는 경우 선언하는 방법이다.
type Example = true | false;
Example
에 마우스를 올려보면, boolean
으로 분류된 것을 볼 수 있다. 구조적 타입 시스템(Structural Type System)의 프로퍼티이다.
다음과 같이 허용되는 string
또는 number
의 리터럴집합을 설명할 때 유니언 타입이 많이 사용된다.
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
유니온은 서로 다른 타입을 처리하는 방법도 제공한다. 예를 들면 문자열이나 문자열이 들어가는 배열을 받는 함수가 있을 수도 있다.
function getLength(obj: string | string[]) {
return obj.length;
}
변수의 타입을 알아내려면 typeof
를 사용하면 된다.
예를들면 다음과 같이 문자열이나 배열이 넘겨졌을 때 다른 변수를 리턴하는 함수를 만들 수도 있다.
function wrapInArray(obj: string | string[]) {
if (typeof obj === "string") {
return [obj];
// ^ = (parameter) obj: string
} else {
return obj;
}
}
JavaScript에서 제네릭이 없는 배열은 요소로 어떤 타입이든 받을 수 있다. 하지만 TypeScript에서 제네릭이 있는 배열은 배열 안의 값을 설명할 수 있다.
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;
제네릭을 사용하는 나만의 타입을 선언할 수도 있다.
interface Backpack<Type> {
add: (obj: Type) => void;
get: () => Type;
}
// This line is a shortcut to tell TypeScript there is a
// constant called `backpack`, and to not worry about where const backpakc: Backpack<string> came from.
declare const backpack: Backpack<string>;
// object is a string, because we declared it above as the variable part of Backpack.
const object = backpack.get();
// Since the backpack variable is a string, you can't pass a number to the add function.
backpack.add(23);
Argument of type 'number' is not assignable to parameter of type 'string'.
TypeScript의 핵심 원칙 중 하나는 타입 검사가 값이 갖는 형태에 집중한다는 것이다. 이는 "duck typing"이나 "structural typing" 이라고도 불린다.
구조적 타입 시스템에서 두 객체가 같은 형태를 가지면 그 두 객체는 같은 타입으로 간주된다.
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// prints "12, 26"
const point = { x: 12, y: 26 };
printPoint(point);
변수 point
는 Point
타입으로 선언된 적이 없지만 TypeScript는 type-check에서 point
의 형태와 Point
의 형태를 비교하고, 둘 다 같은 형태이기 때문에 위의 코드는 통과한다.
형태 일치(shpe-matching)는 일치시킬 객체의 필드의 하위 집합만 요구한다.
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);
Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
마지막으로 구조적으로 클래스와 객체가 형태를 따르는 방법에는 차이가 없다.
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"
객체나 클래스에 필요한 모든 속성이 존재한다면, TypeScript는 구현 세부 정보에 관계 없이 일치한다고 판단한다.