타입스크립트-2

시작하기 전…

typescriptlang in 5 minutes

위에글의 번역본을 가져와 글을 작성하였다.

TypeScript for JavaScript Programmers

프로그래밍 언어에서 TypeScript와 JavaScript의 관계는 다소 독특하다. TypeScript은 JavaScript 위에 레이어로서 자리잡고 있는데, JavaScript의 기능들을 제공하면서 그 위에 자체 레이어를 추가합니다. 이 레이어가 TypeScript 타입 시스템이다.

JavaScript는 이미 string, number, object, undefined 같은 원시 타입을 가지고 있지만, 전체 코드베이스에 일관되게 할당되었는지는 미리 확인해 주지 않는다. TypeScript는 이 레이어로서 동작한다.

이는 이미 존재하고 잘 동작하는 JavaScript 코드는 동시에 TypeScript 코드라는 의미지만, TypeScript의 타입 검사기는 사용자가 생각한 일과 JavaScript가 실제로 하는 일 사이의 불일치를 강조할 수 있다.

이 튜토리얼은 TypeScript가 추가하는 타입 시스템 언어 확장을 이해하는데 중점을 두고 타입 시스템에 대한 5분 개요를 제공한다.

타입 추론 (Types by Inference)

TypeScript는 JavaScript 언어를 알고 있으며 대부분의 경우 타입을 생성해줄 것입니다. 예를 들어 변수를 생성하면서 동시에 특정 값에 할당하는 경우, TypeScript는 그 값을 해당 변수의 타입으로 사용한다.

1
2
let helloWorld = "Hello World";
// ^?

JavaScript가 동작하는 방식을 이해함으로써 TypeScript는 JavaScript 코드를 받아들이면서 타입을 가지는 타입 시스템을 구축할 수 있다. 이는 코드에서 타입을 명시하기 위해 추가로 문자를 사용할 필요가 없는 타입 시스템을 제공하며, 위의 예제에서 TypeScript가 helloWorld가 string임을 알게 되는 방식이다.

JavaScript와 함께 VS Code를 사용하고 작업을 할 때 편집기의 자동 완성 기능을 사용해왔을 것이다. 이는 TypeScript에 필수불가결한 JavaScript에 대한 이해가 JavaScript 작업을 개선하기 위해 내부적으로 사용되었기 때문이다.

타입 정의하기 (Defining Types)

JavaScript는 다양한 디자인 패턴을 가능하게 하는 동적 언어입니다. 몇몇 디자인 패턴은 자동으로 타입을 제공하기 힘들 수 있는데 (동적 프로그래밍을 사용하고 있을 것이기 때문에) 이러한 경우에 TypeScript는 TypeScript에게 타입이 무엇이 되어야 하는지 명시 가능한 JavaScript 언어의 확장을 지원합니다.

다음은 name: stringid: number을 포함하는 추론 타입을 가진 객체를 생성하는 예제입니다.

1
2
3
4
const user = {
name: "Hayes",
id: 0,
};

이 객체의 형태를 명시적으로 나타내기 위해서는 interface 로 선언합니다.

1
2
3
4
interface User {
name: string;
id: number;
}

이제 변수 선언 뒤에 : TypeName의 구문을 사용해 JavaScript 객체가 새로운 interface의 형태를 따르고 있음을 선언할 수 있습니다.

1
2
3
4
5
6
7
8
9
interface User {
name: string;
id: number;
}
// ---cut---
const user: User = {
name: "Hayes",
id: 0,
};

해당 인터페이스에 맞지 않는 객체를 생성하면 TypeScript는 경고를 줍니다.

1
2
3
4
5
6
7
8
9
// @errors: 2322
interface User {
name: string;
id: number;
}
const user: User = {
username: "Hayes",
id: 0,
};

JavaScript는 클래스와 객체 지향 프로그래밍을 지원하기 때문에, TypeScript 또한 동일합니다. - 인터페이스는 클래스로도 선언할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

인터페이스는 함수에서 매개변수와 리턴 값을 명시하는데 사용되기도 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
// @noErrors
interface User {
name: string;
id: number;
}
// ---cut---
function getAdminUser(): User {
//...
}
function deleteUser(user: User) {
// ...
}

JavaScript에서 사용할 수 있는 적은 종류의 원시 타입이 이미 있습니다.: boolean, bigint, null, number, string, symbol, object와 undefined는 인터페이스에서 사용할 수 있습니다. TypeScript는 몇 가지를 추기해 목록을 확장합니다.

예를 들어, any (무엇이든 허용합니다), unknown (이 타입을 사용하는 사람이 타입이 무엇인지 선언했는가를 확인하십시오), never (이 타입은 발생될 수 없습니다) void (undefined를 리턴하거나 리턴 값이 없는 함수).

타입을 구축하기 위한 두 가지 구문이 있다는 것을 꽤 빠르게 알 수 있을 것입니다.: Interfaces and Types - interface를 우선적으로 사용하고 특정 기능이 필요할 때 type을 사용해야 합니다.

타입 구성 (Composing Types)

객체들을 조합하여 더 크고 복잡한 객체를 만드는 방법과 유사하게 TypeScript에 타입으로 이를 수행하는 도구가 있습니다. 여러가지 타입을 이용하여 새 타입을 작성하기 위해 일상적인 코드에서 가장 많이 사용되는 두 가지 코드로는 유니언(Union)과 제네릭(Generic)이 있습니다.

유니언 (Unions)

유니언은 타입이 여러 타입 중 하나일 수 있음을 선언하는 방법입니다. 예를 들어, boolean 타입을 true 또는 false로 설명할 수 있습니다:

1
type MyBool = true | false;

참고: MyBool위에 마우스를 올린다면, boolean으로 분류된 것을 볼 수 있습니다 - 구조적 타입 시스템의 프로퍼티며, 나중에 살펴보겠습니다.

유니언 타입이 가장 많이 사용된 사례 중 하나는 값이 다음과 같이 허용되는 string 또는 number의 리터럴집합을 설명하는 것입니다:

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

유니언은 다양한 타입을 처리하는 방법을 제공하는데, 예를 들어 array 또는 string을 받는 함수가 있을 수 있습니다.

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

TypeScript는 코드가 시간에 따라 변수가 변경되는 방식을 이해하며, 이러한 검사를 사용해 타입을 골라낼 수 있습니다.

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)

예를 들어, typeof obj === “string”을 이용하여 string과 array를 구분할 수 있으며 TypeScript는 객체가 다른 코드 경로에 있음을 알게 됩니다.

1
2
3
4
5
6
7
8
function wrapInArray(obj: string | string[]) {
if (typeof obj === "string") {
return [obj];
// ^?
} else {
return obj;
}
}

제네릭 (Generics)

TypeScript 제네릭 시스템에 대해 자세히 알아볼 수 있지만, 1분 정도의 수준 높은 설명을 하기 위해, 제네릭은 타입에 변수를 제공하는 방법입니다.

배열이 일반적인 예시이며, 제네릭이 없는 배열은 어떤 것이든 포함할 수 있습니다. 제네릭이 있는 배열은 배열 안의 값을 설명할 수 있습니다.

1
2
3
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;

제네릭을 사용하는 고유 타입을 선언할 수 있습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// @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);

구조적 타입 시스템 (Structural Type System)

TypeScript의 핵심 원칙 중 하나는 타입 검사가 값이 있는 _형태_에 집중한다는 것입니다. 이는 때때로 “덕 타이핑(duck typing)” 또는 “구조적 타이핑” 이라고 불립니다.

구조적 타입 시스템에서 두 객체가 같은 형태를 가지면 같은 것으로 간주됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
interface Point {
x: number;
y: number;
}

function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}

// "12, 26"를 출력합니다
const point = { x: 12, y: 26 };
printPoint(point);

point변수는 Point타입으로 선언된 적이 없지만, TypeScript는 타입 검사에서 point의 형태와 Point의 형태를 비교합니다. 둘 다 같은 형태이기 때문에, 통과합니다.

형태 일치에는 일치시킬 객체의 필드의 하위 집합만 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// @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);

마지막으로, 정확하게 마무리 짓기 위해, 구조적으로 클래스와 객체가 형태를 따르는 방법에는 차이가 없습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// @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"

객체 또는 클래스에 필요한 모든 속성이 존재한다면, TypeScript는 구현 세부 정보에 관계없이 일치하게 봅니다.

타입스크립트-1

타입스크립트?

  • Language
  • Typed Superset of JavaScript
  • compiles to plain JavaScript

자바스크립트 그리고 확장

TypeScript는 JavaScript에 구문을 추가하여 편집기와 의 긴밀한 통합을 지원하며, 편집기에서 초기에 오류를 포착할 수 있다.

신뢰할 수 있는 결과

TypeScript 코드는 JavaScript가 실행되는 모든 환경에서 실행되는 JavaScript로 변환 된다.
예) 브라우저, Node.js 또는 Deno 및 앱

규모에 따른 안전

TypeScript는 JavaScript를 이해하고 유형 추론을 사용 하여 추가 코드 없이도 훌륭한 도구를 제공한다.

JavaScript는 웹 페이지에 사소한 상호작용을 추가하기 위한 작은 스크립팅 언어로 시작하여, 규모에 상관없이 프론트엔드와 백엔드 애플리케이션에서 선택 가능한 언어로 성장했습니다. JavaScript로 작성된 프로그램의 크기, 범위 및 복잡성은 기하급수적으로 커졌지만, 다른 코드 단위 간의 관계를 표현하는 JavaScript 언어의 능력은 그렇지 못했습니다. JavaScript의 다소 특이한 런타임 의미 체계(runtime semantics)와 더불어, 언어와 프로그램 복잡성 간의 불일치는 JavaScript 개발을 규모에 맞게 관리하기 어려운 작업으로 만들었습니다.

  • 프로그래머들이 작성하는 가장 흔한 오류는 타입 오류이다.
  • 다른 종류의 값이 예상되는 곳에 특정한 값이 사용된 경우이다.
    (이는 단순한 오타, 라이브러리 API를 이해하지 못한 것, 런타임 동작에 대한 잘못된 가정 또는 다른 오류 때문일 수 있다.)
  • TypeScript의 목표는 JavaScript 프로그램의 정적 타입 검사자이다.
    (즉, 코드가 실행되기 전에 실행하고(정적), 프로그램 타입이 정확한지 확인하는 도구(타입 검사)이다.)

JavaScript Short History

JavaScript(ECMAScript으로도 알려져있는)는 처음에 브라우저를 위한 스크립팅 언어로 만들어졌습니다. JavaScript가 처음 나왔을 때, 수십 줄 이상의 코드를 작성하는 것은 다소 이례적인 일이었기에 웹 페이지 속 짧은 코드들을 위해 사용할 것으로 여겨졌습니다. 때문에, 초기 웹 브라우저들은 수십 줄 이상의 코드를 실행하는데 오래 걸렸습니다. 그러나 시간이 흘러 JS가 점점 더 유명해지면서, 웹 개발자들은 JS를 이용해 상호작용을 하는 경험을 하기 시작했습니다.

웹 브라우저 개발자들은 위와 같이 늘어나는 JS 사용량에 대하여 실행 엔진(동적 컴파일)을 최적화시키고 최적화 된 것을 이용해 할 수 있는 일(API 추가)을 확장하여 웹 개발자가 더 많이 JS를 사용할 수 있게 했습니다.

현대 웹사이트에서, 브라우저는 수십만 줄의 코드로 구성된 어플리케이션을 자주 실행합니다. 이는 정적 페이지의 간단한 네트워크로 시작해서, 모든 종류의 만족스러울만한 어플리케이션 을 위한 플랫폼으로 성장한 “웹”의 길고 점진적인 성장입니다.

이외에도, JS는 node.js를 사용하여 JS 서버들을 구현하는 것처럼, 브라우저 맥락에서 벗어나는 일에 사용하기 충분할 정도로 유명해졌습니다. “어디서든 작동됨”과 같은 JS의 성질은 교차 플랫폼(cross-platform) 개발을 위한 매력적인 선택지이기도 합니다. 오늘날 많은 개발자들은 오직 JavaScript만을 이용하여 전체 스택을 프로그래밍하고 있다.

요약하자면, 우리에게는 빠른 사용을 위해 설계되었으며 수백만 줄의 어플리케이션들을 작성하기 위해 만들어진 완벽한 도구인 JavaScript가 있습니다. 모든 언어는 각자의 별난 점 - 이상한 점과 놀랄만한 점이 있으며, JavaScript의 자랑스럽지만은 않은 시작은 많은 문제를 만들게 되었습니다.

예를 들어..

  • JavaScript의 동일 연산자는 (==) 인수를 강제로 변환하여(coerces), 예기치 않은 동작을 유발합니다:
1
2
3
4
5
6
if ("" == 0) {
// 참입니다! 근데 왜죠??
}
if (1 < x < 3) {
// *어떤* x 값이던 참입니다!
}
  • JavaScript는 또한 존재하지 않는 프로퍼티의 접근을 허용합니다:
1
2
3
const obj = { width: 10, height: 15 };
// 왜 이게 NaN이죠? 철자가 어렵네요!
const area = obj.width * obj.heigth;

대부분의 프로그래밍 언어는 이런 종류의 오류들이 발생하면 오류를 표출해주고, 일부는 코드가 실행되기 전인 컴파일 중에 오류를 표출해줍니다. 작은 프로그램을 작성할 때에는, 이런 이상한 점들이 화를 돋구지만 관리는 가능합니다. 그러나 수백 또는 수천 줄의 어플리케이션들을 작성할 때에는, 이러한 지속적 놀라움들은 심각한 문제를 야기한다.

TypeScript: 정적 타입 검사자 (TypeScript: A Static Type Checker)

앞서 몇 언어는 버그가 많은 프로그램을 아예 실행시키지 않는다고 했습니다. 프로그램을 실행시키지 않으면서 코드의 오류를 검출하는 것을 정적 검사 라고 합니다. 어떤 것이 오류인지와 어떤 것이 연산 되는 값에 기인하지 않음을 정하는 것이 정적 타입 검사입니다.

정적 타입 검사자 인 TypeScript는 프로그램을 실행시키기 전에 값의 종류 를 기반으로 프로그램의 오류를 찾습니다. 예를 들어, 위의 마지막 예시에 오류가 있는 이유는 obj의 타입 때문입니다.

  • 다음은 TypeScript에서 볼 수 있는 오류입니다:
1
2
3
// @errors: 2551
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;

타입이 있는 JavaScript의 상위 집합 (A Typed Superset of JavaScript)

그렇다면 TypeScript는 JavaScript와 어떤 관계일까요?

구문 (Syntax)
TypeScript는 JS의 구문이 허용되는, JavaScript의 상위 집합 언어입니다. 구문은 프로그램을 만들기 위해 코드를 작성하는 방법을 의미합니다. 예를 들어, 다음 코드는 )이 없으므로 구문 오류입니다:

1
2
// @errors: 1005
let a = (4

TypeScript는 독특한 구문 때문에 JavaScript 코드를 오류로 보지 않습니다. 즉, 어떻게 작성돼있는지 모르지만 작동하는 JavaScript 코드를 TypeScript 파일에 넣어도 잘 작동합니다.

타입 (Types)
그러나 TypeScript는 다른 종류의 값들을 사용할 수 있는 방법이 추가된, 타입이 있는 상위 집합입니다. 위의 obj.heigth 오류는 구문 오류가 아닌, 값의 종류(타입)를 잘못 사용해서 생긴 오류입니다.

또 다른 예시로, 아래와 같은 JavaScript 코드가 브라우저에서 실행될 때, 다음과 같은 값이 출력될 것입니다:

1
console.log(4 / []);

구문적으로 옳은(syntactically-legal) 위 코드는 JavaScript에서 NaN을 출력합니다. 그러나 TypeScript는 배열로 숫자를 나누는 연산이 옳지 않다고 판단하고 오류를 발생시킵니다:

1
2
// @errors: 2363
console.log(4 / []);

실제로 어떤 일이 일어나는지 보려는 의도로 숫자를 배열로 나눌 수 있지만, 대부분은 프로그래밍 실수입니다. TypeScript의 타입 검사자는 일반적인 오류를 최대한 많이 검출하면서 올바른 프로그램을 만들 수 있게 설계되었습니다.
(TypeScript가 코드를 얼마나 엄격하게 검사할 수 있는지에 대한 설정 또한 존재)

만약 JavaScript 파일의 코드를 TypeScript 코드로 옮기면, 코드를 어떻게 작성했는지에 따라 타입 오류 를 볼 수 있습니다. 이는 코드 상의 문제이거나, TypeScript가 지나치게 보수적인 것일 수 있습니다. 위와 같은 오류를 제거하기 위해 가이드는 다양한 TypeScript 구문을 추가하는 방법을 보여줍니다.

런타임 특성 (Runtime Behavior)

TypeScript는 JavaScript의 런타임 특성 을 가진 프로그래밍 언어입니다. 예를 들어, JavaScript에서 0으로 나누는 행동은 런타임 예외로 처리하지 않고 Infinity값을 반환합니다. 논리적으로, TypeScript는 JavaScript 코드의 런타임 특성을 절대 변화시키지 않습니다.

즉 TypeScript가 코드에 타입 오류가 있음을 검출해도, JavaScript 코드를 TypeScript로 이동시키는 것은 같은 방식으로 실행시킬 것을 보장합니다

JavaScript와 동일한 런타임 동작을 유지하는 것은 프로그램 작동을 중단시킬 수 있는 미묘한 차이를 걱정하지 않고 두 언어 간에 쉽게 전환할 수 있도록 하기 위한 TypeScript의 기본적인 약속입니다.

삭제된 타입 (Erased Types)

개략적으로, TypeScript의 컴파일러가 코드 검사를 마치면 타입을 삭제해서 결과적으로 “컴파일된” 코드를 만듭니다. 즉 코드가 한 번 컴파일되면, 결과로 나온 일반 JS 코드에는 타입 정보가 없습니다.

타입 정보가 없는 것은 TypeScript가 추론한 타입에 따라 프로그램의 특성 을 변화시키지 않는다는 의미입니다. 결론적으로 컴파일 도중에는 타입 오류가 표출될 수 있지만, 타입 시스템 자체는 프로그램이 실행될 때 작동하는 방식과 관련이 없습니다.

마지막으로, TypeScript는 추가 런타임 라이브러리를 제공하지 않습니다. TypeScript는 프로그램은 JavaScript 프로그램과 같은 표준 라이브러리 (또는 외부 라이브러리)를 사용하므로, TypeScript 관련 프레임워크를 추가로 공부할 필요가 없습니다.

컴파일

  • 컴파일이 필요 O
  • 컴파일러가 필요 O
  • 컴파일하는 시점 O
  • 컴파일된 결과물을 실행

설치

1
2
npm i typescript -g
# npm init 후 npm i typescript 명령어로 프로젝트에만 tsc 사용도 가능

Visual Studio Code 2015, 2017 이후로는 디폴트로 설치됨

환경설정

1
2
tsc --init
# tsconfig.json 컴파일 시 옵션을 설정

TypeScript Compiler

  • VS Code에 컴파일러가 내장되어 있다.
  • 내장된 컴파일러버전은 VSCode가 업데이트 되면서 올라간다.

만약 VS Code에 내장된 버전이 아닌 프로젝트 환경 전용으로 사용하고 싶다면 npm install typesript를 package.json 쪽에 버전을 명시하여 사용할 수 있다.

레퍼런스