TypeScript는 자바스크립트와는 독특한 관계를 맺고 있습니다. TypeScript는 자바스크립트의 모든 기능을 제공할 뿐만 아니라, 그 위에 추가적인 계층도 제공하는데, 이는 TypeScript 고유의 타입 시스템입니다.

예를 들어, 자바스크립트는 string이나 number와 같은 언어 기본형을 제공하지만, 이들이 일관되게 할당되었는지를 검사하지는 않습니다. TypeScript는 이를 검사합니다.

즉, 기존의 작동하는 자바스크립트 코드는 이미 TypeScript 코드이기도 합니다. TypeScript의 주요 이점은 코드 내의 예상치 못한 동작을 강조하여, 버그 발생 가능성을 낮추는 것입니다.

이 튜토리얼은 TypeScript에 대한 간략한 개요를 제공하며, 특히 타입 시스템에 초점을 맞추고 있습니다.

추론에 의한 타입

TypeScript는 자바스크립트 언어를 알고 있으며, 많은 경우에 당신을 위해 타입을 생성해줍니다. 예를 들어, 변수를 생성하고 특정 값을 할당할 때 TypeScript는 그 값을 타입으로 사용할 것입니다.

let helloWorld = "Hello World"; //Javascript
let helloWorld: string = "Hello World"; //Typescript

자바스크립트의 작동 방식을 이해함으로써, TypeScript는 자바스크립트 코드를 받아들이지만 타입을 가지는 타입 시스템을 구축할 수 있습니다. 이것이 TypeScript가 코드에 추가적인 문자를 넣지 않고도 타입 시스템을 제공하는 방법입니다. 그래서 TypeScript가 위의 예제에서 helloWorld가 string임을 알 수 있는 것입니다.

Visual Studio Code에서 자바스크립트를 작성해 본 적이 있다면, 에디터의 자동 완성 기능을 경험했을 것입니다. Visual Studio Code는 바로 TypeScript를 사용하여 자바스크립트 작업을 용이하게 해줍니다.

타입 정의하기

자바스크립트에서는 다양한 디자인 패턴을 사용할 수 있습니다. 그러나 일부 디자인 패턴은 동적 프로그래밍을 사용하는 패턴과 같이 타입이 자동으로 추론될 수 없게 만듭니다. 이러한 경우를 커버하기 위해 TypeScript는 자바스크립트 언어의 확장을 지원하며, 이를 통해 TypeScript에 타입이 무엇이어야 하는지 알려줄 수 있는 영역을 제공합니다.

예를 들어, name: string과 id: number를 포함하는 추론 타입의 객체를 만들기 위해서는 다음과 같이 작성할 수 있습니다:

const user = {
  name: "Hayes",
  id: 0,
};

이 객체의 형태를 명시적으로 설명하기 위해 인터페이스 선언을 사용할 수 있습니다:

interface User {
  name: string;
  id: number;
}

변수 선언 뒤에 `: TypeName`과 같은 문법을 사용하여 자바스크립트 객체가 새로운 인터페이스의 형태를 따른다고 선언할 수 있습니다:

const user: User = {
  name: "Hayes",
  id: 0,
};

제공하는 객체가 선언한 인터페이스와 일치하지 않으면, TypeScript는 경고를 보냅니다:

interface User {
  name: string;
  id: number;
}
 
const user: User = {
  username: "Hayes",
=> 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'.
  id: 0,
};

자바스크립트가 클래스와 객체지향 프로그래밍을 지원하므로, TypeScript도 지원합니다. 클래스에서 인터페이스 선언을 사용할 수 있습니다:

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);

함수의 매개변수와 반환 값에 인터페이스를 사용하여 주석을 달 수도 있습니다:

function deleteUser(user: User) {
  // ...
}
 
function getAdminUser(): User {
  //...
}

자바스크립트에는 boolean, bigint, null, number, string, symbol, undefined와 같은 소수의 원시 타입들이 이미 있습니다. 이러한 타입들을 인터페이스에서 사용할 수 있습니다. TypeScript는 any (아무 것이나 허용함), unknown (이 타입을 사용하는 사람이 타입을 선언하도록 합니다), never (이 타입이 발생할 가능성이 없음), void (undefined를 반환하거나 반환 값이 없는 함수) 등 몇 가지를 추가로 제공합니다.

타입을 구축하기 위해 두 가지 문법을 사용한다는 것을 확인할 수 있습니다: 인터페이스와 타입. 인터페이스 사용을 선호해야 합니다. 특정 기능이 필요할 때 타입을 사용하세요.

타입 조합하기

TypeScript를 사용하면 간단한 타입들을 결합하여 복잡한 타입을 만들 수 있습니다. 이를 수행하는 두 가지 인기 있는 방법이 있습니다: 유니언과 제네릭입니다.

유니언
유니언을 사용하면 타입이 여러 가지 타입 중 하나가 될 수 있음을 선언할 수 있습니다. 예를 들어, boolean 타입이 true 또는 false가 될 수 있음을 설명할 수 있습니다:

type MyBool = true | false;

참고: MyBool 위로 마우스를 올리면, boolean으로 분류된다는 것을 볼 수 있습니다. 이것은 구조적 타입 시스템의 성질입니다. 아래에서 더 설명하겠습니다.

유니언 타입을 사용하면, 값이 될 수 있는 문자열이나 숫자 리터럴 집합을 설명하는 데 유용합니다:

type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;

유니언은 다른 타입을 처리하는 방법도 제공합니다. 예를 들어, 배열이나 문자열을 인자로 취하는 함수가 있을 수 있습니다:

function getLength(obj: string | string[]) {
  return obj.length;
}

변수의 타입을 알아보기 위해서는 typeof를 사용하세요:

Type	Predicate
string	typeof s === "string"
number	typeof n === "number"
boolean	typeof b === "boolean"
undefined	typeof undefined === "undefined"
function	typeof f === "function"
array	Array.isArray(a)


예를 들어, 문자열이나 배열이 전달되는지에 따라 다른 값을 반환하도록 함수를 만들 수 있습니다:

function wrapInArray(obj: string | string[]) {
  if (typeof obj === "string") {
    return [obj];
            
(parameter) obj: string
  }
  return obj;
}

제네릭

제네릭은 타입에 변수를 제공합니다. 일반적인 예로 배열이 있습니다. 제네릭이 없는 배열은 아무 것이나 포함할 수 있습니다. 제네릭이 있는 배열은 배열이 포함하는 값들을 설명할 수 있습니다.

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 it 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 logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}
 
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);

point 변수는 Point 타입으로 선언된 적이 없습니다. 그러나 TypeScript는 point의 형태와 Point의 형태를 타입 검사로 비교합니다. 두 형태가 같기 때문에 코드가 통과됩니다.

형태 일치는 객체의 필드 일부만 일치하면 됩니다.

const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"
 
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"
 
const color = { hex: "#187ABF" };
logPoint(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);
logPoint(newVPoint); // logs "13, 56"

객체나 클래스에 필요한 모든 속성이 있으면, TypeScript는 이들이 일치한다고 할 것입니다. 구현 세부 사항과 관계 없이 말이죠.