Typescript
Introduction
- TypeScript는 컴파일하면 JavaScript가 되는(compile-to-JavaScript) 언어이며, 컴파일 시점에 타입 체크를 하고, 전통적인 객체기반 프로그래밍 패턴을 도입하는 것 이외에도 강력한 기능들을 JavaScript 에 추가한다.
- “JavaScript는 특이한 점이 좀 있는데, 컴파일러에게 이런 것들을 알려주고 전부 체크하기만 하면 된다.” - Anders Hejlsberg
장점
정적타입
- TypeScript를 사용하는 가장 큰 이유 중 하나는 정적 타입을 지원한다는 것
function sum(a: number, b: number) {
return a + b;
}
sum('x', 'y');
// error TS2345: Argument of type '"x"' is not assignable to parameter of type 'number'.
도구의 지원
강력한 객체지향 프로그래밍 지원
- 인터페이스, 제네릭 등과 같은 강력한 객체지향 프로그래밍 지원은 크고 복잡한 프로젝트의 코드 기반을 쉽게 구성할 수 있도록 도우며, Java, C# 등의 클래스 기반 객체지향 언어에 익숙한 개발자가 자바스크립트 프로젝트를 수행하는 데 진입 장벽을 낮추는 효과도 있다.
ES6 / ES Next 지원
Angular
설치
$ npm install -g typescript
$ tsc -v
Version 2.8.3
yarn global add typescript
기초 사용
- TypeScript 컴파일러(tsc)는 TypeScript 파일(.ts)을 자바스크립트 파일로 트랜스파일링한다. 이때 트랜스파일링된 person.js의 자바스크립트 버전은 ES3이다. 이는 TypeScript 컴파일 타겟 자바스크립트 기본 버전이 ES3이기 때문이다.
// person.ts
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
return "Hello, " + this.name;
}
}
const person = new Person('Lee');
console.log(person.sayHello());
$ tsc person
// person.js - ES3
var Person = /** @class */ (function () {
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
return "Hello, " + this.name;
};
return Person;
}());
var person = new Person('Lee');
console.log(person.sayHello());
- 만약, 자바스크립트 버전을 변경하려면 컴파일 옵션에 –target 또는 -t를 사용한다. 현재 tsc가 지원하는 자바스크립트 버전은 ‘ES3’(default), ‘ES5’, ‘ES2015’, ‘ES2016’, ‘ES2017’, ‘ES2018’, ‘ES2019’, ‘ESNEXT’이다. 예를 들어, ES6 버전으로 트랜스파일링을 실행하려면 아래와 같이 옵션을 추가한다.
$ tsc person -t ES2015
// person.js
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return "Hello, " + this.name;
}
}
const person = new Person('Lee');
console.log(person.sayHello());
tsconfig
- 매번 옵션을 지정하는 것은 번거로우므로 tsc 옵션 설정 파일을 생성하도록 하자. tsc 명령어 뒤에 파일명을 지정하면 tsconfig.json이 무시되므로 주의하기 바란다.
$ tsc --init
message TS6071: Successfully created a tsconfig.json file.
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [],
$ tsc person //tsconfig.json이 무시된다.
$ tsc //파일명을 지정하지 않으면 프로젝트 폴더 내의 모든 TypeScript 파일이 모두 트랜스파일링된
--watch 또는 -w 옵션을 사용하면 트랜스파일링 대상 파일의 내용이 변경되었을 때 이를 감지하여 자동으로 트랜스파일링이 실행된다.
$ tsc --watch
21:23:30 - Compilation complete. Watching for file changes.
//tsconfig.json에 watch 옵션을 추가
{
// ...
"watch": true
}
[오전 12:40:06] File change detected. Starting incremental compilation...
[오전 12:40:07] Found 0 errors. Watching for file changes.
yarn add tsc-watch --dev
이후 바로 yarn add typescript를 해준다
tsc-watch가 global패키지를 읽지 못하는 듯하다. 그래서 새로 설치한다.
Typescript Compile Option을 설정할 수 있다.
- files > exlucde > include
@types
compileOptions: target, lib
- target
- ts file을 js file로 compile할 때 어떤 버전의 js 스펙을 적용할 지 ex) es3, es5
- default: es3
- lib
- 기본 type definition 라이브러리를 어떤 것을 사용할 지
- lib 지정안할 경우,
- target = es3, default로 lib.d.ts
- target = es5, default로 dom,es5,scripthost
- target = es6, default로 dom,es6,dom.iterable,scripthost
- lib를 지정하면 그 lib 배열로만 라이브러리를 사용한다.
- 빈 [] => ‘no definition found 샬라샬라’
compileOptions: outDir, outFile
- outFile -> 하나의 file로 type을 만들 경우
- outDir -> 디렉토리 그대로 type 만들 경우
compileOptiosn: module
- 컴파일 된 모듈의 결과물을 어떤 모듈 시스템으로 할지 결정 module ex) commonJS, systemJS 등등
- target = es6 -> es6가 디폴트 / 아닐 경우 commonJS가 디폴트
- moduleResoliution -> ts 소스에서 모듈을 사용하는 방식 지정. Classic 아니면 Node 이다.
- paths 와 baseUrl은 가져올때 사용하는 것, 상대경로 방식이 아닌 baseUrl로 꼭지점과 paths 안의 키/밸류로 모듈을 가져가는 방식.
- rootDirs: 배열 안에서 상대 경로를 찾는 방식. -> 모듈 가져올 떄 모듈 path에 대한 root지정.
Type assertions
- 형변환과는 다르다. -> 실제 데이터 구조를 바꾸지 않음.
- 컴파일러에게 ‘Type이 이것이다’ 라고 알려주는 것을 의미한다.
- 문법적으로는 2가지 방법이 있다.
- 변수 as 강제할타입
- <강제할타입>변수 강제할타입>
타입 별칭 (별명)
- Primitve, Union Type, Tuple
- Interface와 비슷하다.
- 기타 직접 작성해야하는 타입을 다른 이름으로 지정할 수 있따.
- 만들어진 타입의 refer 로 사용하는 것이지 타입을 새로 만드는것이 아니다.
- **Interface 와의 차이점
- Type Alias는 실제 Type이 새로 정의된 것이 아닌 해당 Type에 대한 refer인 반면, Interface는 새로 정의한 Type이다.
- Type Alias는 Type Alias끼리 상속 불가능.
문법
타입 표기 (Type Annotation)
- value: type 의 형태로 표기
const areYouCool: boolean = true;
const hasType: Object = {
TypeScript: true,
JavaScript: false
};
기본 타입
- boolean, number, string, null, undefined, any, void (null, undefined), never
let bool: any = true;
bool = 3;
bool = 'whatever';
bool = {};
function alwaysThrow(): never {
throw new Error(`I'm a wicked function!`);
}
배열과 튜플
const pibonacci: number[] = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
const myFavoriteBeers: string[] = ['Imperial Stout', 'India Pale Ale', 'Weizenbock'];
const pibonacci: Array<number> = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
const myFavoriteBeers: Array<string> = ['Imperial Stout', 'India Pale Ale', 'Weizenbock'];
const nameAndHeight: [string, number] = ['안희종', 176]
nameAndHeight.push(42); // no error
객체
- 자바스크립트의 오브젝트 리터럴을 정의하듯 중괄호({})를 이용해 객체 타입(object type)을 표현할 수 있다.
const user: { name: string; height: number; } = { name: '안희종', height: 176 };
- 구분자로 콤마(,) 뿐만 아니라 세미콜론(;)을 사용할 수 있다.
- 물음표(?)를 붙여 해당 속성이 존재하지 않을 수도 있음을 표현
const userWithUnknownHeight: { name: string; height?: number; } = {
name: '김수한무'
};
- readonly 키워드를 붙여 해당 속성의 재할당을 막을 수 있다. readonly 키워드가 붙은 속성은 const 키워드를 이용한 변수의 정의와 비슷하게 동작한다.
const user: {
readonly name: string;
height: numer;
} = { name: '안희종', height: 176 };
user.name = '종희안'; // error TS2540: Cannot assign to 'name' because it is a constant or a read-only property.
타입 별칭
- 타입 별칭(type alias)을 이용해 이미 존재하는 타입에 다른 이름을 붙여 복잡한 타입을 간단하게 쓸 수 있다
type NewType = Type;
type UUID = string;
type Height = number;
type AnotherUUID = UUID;
type Animals = Animal[];
type User = {
name: string;
height: number;
};
함수
function sum(a: number, b: number): number {
return (a + b);
}
function logGreetings(name: string): void {
console.log(`Hello, ${name}!`);
}
function greetings(name: string = 'stranger'): void {
console.log(`Hello, ${name}`);
}
function fetchVideo(url: string, subtitleLanguage?: string) {
const option = { url };
if (subtitleLanguage) {
option.subtitleLanguage = true;
}
/* ... */
}
const yetAnotherSum: (a: number, b: number) => number = sum;
const onePlusOne: () => number = () => 2;
const arrowSum: (a: number, b: number) => number = (a, b) => (a + b);
type SumFunction = (a: number, b: number) => number;
const definitelySum: SumFunction = (a, b) => (a + b);
- 함수 오버로딩
- 함수는 하나 이상의 타입 시그니처를 가질 수 있다.
- 함수는 단 하나의 구현을 가질 수 있다.
function double(str: string): string;
function double(num: number): number;
function double(arr: boolean[]): boolean[];
function double(arg) {
if (typeof arg === 'string') {
return `${arg}${arg}`;
} else if (typeof arg === 'number') {
return arg * 2;
} else if (Array.isArray(arg)) {
return arg.concat(arg);
}
}
const num = double(3); // number
const str = double('ab'); // string
const arr = double([true, false]); // boolean[]
- This 타입
interface HTMLElement {
tagName: string;
/* ... */
}
interface Handler {
(this: HTMLElement, event: Event, callback: () => void): void;
}
let cb: any;
// 실제 함수 매개변수에는 this가 나타나지 않음
const onClick: Handler = function(event, cb) {
// this는 HTMLElement 타입
console.log(this.tagName);
cb();
}
//this의 타입을 void로 명시한다면 함수 내부에서 this에 접근하는 일 자체를 막을 수 있다
interface NoThis {
(this: void): void;
}
const noThis: NoThis = function() {
console.log(this.a); // Property 'a' does not exist on type 'void'.
}
제너릭
- 제너릭을 이용해 여러 타입에 대해 동일한 규칙을 갖고 동작하는 타입을 정의할 수 있다.
function getFirstElem(arr: string[]): string;
function getFirstElem(arr: number[]): number;
function getFirstElem(arr) {
if (!Array.isArray(arr)) {
throw new Error('getFirstElemOrNull: Argument is not array!');
}
if (arr.length === 0) {
throw new Error('getFirstElemOrNull: Argument is an empty array!');
}
return arr[0] ? arr[0] : null;
}
- 타입 변수
//타입변수 활용 제너릭 함수
function getFirstElem<T>(arr: T[]): T {
/* 함수 본문 */
}
- 여러 타입에 대해 동작하는 요소를 정의하되, 해당 요소를 사용할 때가 되어야 알 수 있는 타입 정보를 정의에 사용하는 것
유니온 타입
- 유니온 타입을 이용해 “여러 경우 중 하나”인 타입을 표현할 수 있다.
function square(value: number, returnString: boolean): number;
function square(value: number, returnString: boolean): string;
function square(value, returnString = false) {
/* 본문 동일 */
}
const mystery: ??? = square(randomNumber, randomBoolean);
function square(value: number, returnString: boolean = false): string | number {
/* 본문 동일 */
}
const stringOrNumber: string | number = square(randomNumber, randomBoolean);
type SquaredType = string | number;
function square(value: number, returnOnString: boolean = false): SquaredType {
/* 본문 동일 */
}
type Fruits =
| Apple
| Banana
| Cherry;
인터섹션 타입
- 인터섹션 타입을 이용해 “여러 경우에 모두 해당”하는 타입을 표현할 수 있다.
type Programmer = { favoriteLanguage: string };
const programmer: Programmer = { favoriteLanguage: 'TypeScript' };
type BeerLover = { favoriteBeer: string };
const beerLover: BeerLover = { favoriteBeer: 'Imperial Stout' };
type BeerLovingProgrammer = Programmar & BeerLover;
type BeerLovingProgrammer2 =
& Programmer
& BeerLover;
열거형
-
유한한 경우의 수를 갖는 값의 집합을 표현하기 위해 사용하는 열거형(enum) 타입
-
숫자 열거형은 number 타입 값에 기반한 열거형이다. 만약 열거형을 정의하며 멤버의 값을 초기화하지 않을 경우, 해당 멤버의 값은 0부터 순차적으로 증가하는 숫자 값을 갖는다
@ 는 this 를 대신한다. 그러므로 this.user 는 @user 이다.
interface
- 인터페이스(interface)를 통해 값이 따라야 할 제약을 타입으로 표현 할 수 있다. 인터페이스 타입을 통해 값의 형태(shape)를, 즉 값이 어떤 멤버를 가져야 하고 각 멤버의 타입은 어때야 하는지를 서술할 수 있다.
interface User {
name: string;
height: number;
}
interface User {
name: string;
readonly height: number;
favoriteLanguage?: string;
}
const author: User = { name: '안희종', height: 176 }; // ok
author.height = 183; // error TS2540: Cannot assign to 'height' because it is a constant or a read-only property.
- 함수 인터페이스
- (매개변수1 이름: 매개변수1 타입, 매개변수2 이름: 매개변수2 타입, …): 반환 타입
interface GetUserName {
(user: User): string;
}
const getUserName: GetUserName = function (user) {
return user.name;
};
- 하이브리드 타입
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
- 제너릭 인터페이스
interface MyResponse<Data> {
data: Data;
status: number;
ok: boolean;
/* ... */
}
inteface User {
name: string;
readonly height: number;
/* ... */
}
const user: MyReponse<User> = await getUserApiCall(userId);
user.name; // 타입 시스템은 user.name이 string임을 알 수 있다.
interface GetData {
<Data>(response: MyResponse<Data>): Data;
}
색인 가능 타입
-
동적인 색인을 표현하는 색인 가능 타입
-
색인 시그니쳐
interface NameHeightMap {
[userName: string]: number | undefined;
}
인터페이스 확장
interface LoggedInUser extends User {
loggedInAt: Date;
}
class
- 클래스(class)를 이용해 객체 지향 프로그래밍 언어와 비슷한 방식으로 코드를 구조화 할 수 있다. 타입스크립트의 클래스는 ES6에 추가된 클래스 문법의 확장으로, 접근 제어자 등의 유용한 추가 기능을 제공한다.
class BarkingDog {
constructor(barkingSound: string) {
console.log(`${barkingSound}!`);
}
}
const barkingDog: BarkingDog = new BarkingDog('월'); // 월!
class BarkingDog {
barkingSound: string;
constructor(barkingSound: string) {
this.barkingSound = barkingSound;
}
bark(): void {
console.log(`${this.barkingSound}!`);
}
}
const barkingDog: BarkingDog = new BarkingDog('월');
barkingDog.bark(); // 월!
클래스 확장
class Base {
baseProp: number;
constructor() {
this.baseProp = 123;
}
}
class Extended extends Base {
extendedProp: number;
constructor() {
super(); // 반드시 이 호출을 직접 해 주어야 한.
this.extendedProp = 456;
}
}
const extended: Extended = new Extended();
console.log(extended.baseProp); // 123
console.log(extended.extendedProp); // 45
static 스태틱 멤버
class Counter {
static count: number = 0;
static increaseCount() {
Counter.count += 1;
}
static getCount() {
return Counter.count;
}
}
console.log(Counter.count); // 0
Counter.increaseCount();
console.log(Counter.getCount()); // 1
Counter.increaseCount();
console.log(Counter.getCount()); // 2
접근 제어자
- public
- 접근 제한이 전혀 존재하지 않으며, 프로그램의 어느 곳에서나 접근 가능
// implicit public member
class Triangle {
vertices: number;
constructor() {
this.vertices = 3;
}
}
// explicit public member
class Triangle {
public vertices: number;
public constructor() {
this.vertices = 3;
}
}
- private
- 해당 클래스 내부의 코드만이 접근 가능
class User {
private password: string;
constructor (password: string) {
this.password = password;
}
}
const yoonha = new User('486');
console.log(yoonha.password);
// error TS2341: Property 'password' is private and only accessible within class 'User'.
- protected
- protected 권한의 멤버는 private과 비슷하게 동작하지만, 서브클래스에서의 접근 또한 허용된다는 점이 다르다.
class User {
protected password: string;
constructor (password: string) {
this.password = password;
}
}
class CarOwner extends User {
carId: string;
constructor (password: string, carId: string) {
super(password);
this.carId = carId;
}
setPassword(newPassword: string) {
this.password = newPassword; // Okay
}
}
- 생성자에서의 접근 제어자
class User {
constructor (public id: string, private password: string) { }
}
//동일한 코드
class User {
public id: string;
private password: string;
constructor (id: string, password: string) {
this.id = id;
this.password = password;
}
}
접근자
class Shape {
private _vertices: number = 3;
getVertices() {
console.log('Vertices getter called.');
return this._vertices;
}
setVertices(value) {
console.log('Vertices setter called.');
this._vertices = value;
}
}
- 읽기 접근을 위한 게터
class Shape {
constructor (public vertices: number) { }
get vertices(): number {
console.log('Vertices getter called.');
return 3;
}
}
const triangle: Shape = new Shape(3);
const vertices = triangle.vertices; // Vertices getter called.
console.log(vertices); // 3
- 쓰기 접근을 위한 세터
class Shape {
private _vertices: number = 3;
get vertices() {
console.log('Vertices getter called.');
return this._vertices;
}
set vertices(value) {
console.log('Vertices setter called.');
this._vertices = value;
}
}
const square = new Shape();
square.vertices = 4; // Vertices setter called.
const vertices = square.vertices; // Vertices getter called.
console.log(vertices); // 4
추상 클랙스
- 추상 클래스는 인스턴스화가 불가능하다는 점에서 일반 클래스와 다르다
- 추상 클래스는 구현을 일부 포함할 수 있다는 점에서 인터페이스와 다르다.
abstract class Animal {
move(): void {
console.log("roaming the earth...");
}
abstract makeSound(): void;
}
- 가상 클래스를 확장하는 서브 클래스는 슈퍼 클래스의 모든 가상 메소드를 구현해야 한다.
class Dog extends Animal { }
// error TS2515: Non-abstract class 'Dog' does not implement inherited abstract member 'makeSound' from class 'Animal'.
인터페이스와 클래스의 관계
interface Animal {
legs: number;
}
class Dog implements Animal {
legs: number = 4;
}
// Okay
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
const point3d: Point3d = {x: 1, y: 2, z: 3};