Typscript Basics

2020. 07. 17

Jaewoo KIM

Microsoft 에서 개발하고 유지/관리하는 오픈 소스

일반 자바스크립트로 컴파일 되는 자바스크립트 상위호환

2012년 10월 처음 릴리즈

  • 크로스 플랫폼 지원: JS 실행되는 모든 플랫폼 사용 가능
  • 객체 지향 언어: 클래스, 인터페이스, 모듈
  • 정적 타입: 컴파일 오류 체크
  • ...

💁‍♂️ 기본 타입

  1. Boolean

  2. Number

  3. String

  4. Array

  5. Tuple

  6. Enum

  7. Any

  8. Void

  9. Null and Undefined

  10. Never

  11. Object

Boolean, Number, String

// Boolean
let isTrue: boolean = true;
let isFalse: boolean = false;

// Number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// String
let fullName: string = `Jaewoo KIM`;
let age: number = 29;
let sentence: string = `Hello, my name is ${ fullName }.

Array, Tuple

// Array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

// Tuple: 요소의 타입과 개수가 고정된 배열
let x: [string, number];
x = ["hello", 10]; // 성공
x = [10, "hello"]; // 오류

console.log(x[0].substring(1)); // 성공
console.log(x[1].substring(1)); // 오류

x[3] = "world"; // 오류, '[string, number]' 타입에는 프로퍼티 '3'이 없습니다.

console.log(x[5].toString()); // '[string, number]' 타입에는 프로퍼티 '5'가 없습니다

Enum

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

// 시작 번호 설정
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

console.log(c) // 2
console.log(Color[1]) // Red
console.log(Color[2]) // Green
console.log(Color[3]) // Blud

관련된 값의 집합에 더 나은 이름을 붙여줄 수 있다

Any

let notSure: any = 4;
notSure = "type이 자유룝다";
notSure = false;

// object와 차이
let notSure: any = 4;
notSure.ifItExists(); // 성공, 런타임에 존재할 것이다
notSure.toFixed();  // 성공, 존재란다

let prettySure: Object = 4;
prettySure.toFixed(); // 프로퍼티 존재하지 않음

let list: any[] = [1, true, "문자"];
list[1] = 100;

알지 못하는 타입을 표현할 때 (3rd party library)

Type assetions

// angle-bracket 사용
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

// as 사용
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

컴파일러 대신 더 구체적인 타입을 명시하는 것

'as' 와 '<>' 취향 차이 동작은 동일함

Type Aliases

type MyType = string;
type YourType = string | number | boolean;
type TUser = {
  name: string,
  age: number,
  isValid: boolean
} | [string, number, boolean];

let userA: TUser = {
  name: 'Neo',
  age: 85,
  isValid: true
};
let userB: TUser = ['Evan', 36, false];

type 키워드를 사용해 새로운 타입 조합을 만들 수 있다

일반적인 경우 둘 이상의 조합으로 구성하기 위해 유니온을 많이 사용

💁‍♂️ 인터페이스

Interface

function printLabel(labeledObj: { label: string }) {
    console.log(labeledObj.label);
}

let myObj1 = {label: "Size 10 Object"}; 
printLabel(myObj1);

let myObj2 = {size: 10, label: "Size 10 Object"};
printLabel(myObj2);


// Property 'label' is missing in type '{ size: number; }' but required in type '{ label: string; }'.(2345)
let myObj3 = {size: 10};
printLabel(myObj3);

클래스 또는 객체를 위한 타입을 지정 할 때 사용되는 문법

최소한 필요한 프로퍼티가 있는지 검사

Interface

interface LabeledValue {
    label: string;
}

// 명시적 구현이 필요없음, 중요한건 형태
function printLabel(labeledObj: LabeledValue) {
    console.log(labeledObj.label);
}

let myObj1 = {label: "Size 10 Object"};
printLabel(myObj1);

// 순서는 상관 없음
let myObj2 = {size: 10, label: "Size 10 Object"};
printLabel(myObj2);

클래스 또는 객체를 위한 타입을 지정 할 때 사용되는 문법

Interface (Optional Properties)

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

객체 프로퍼티의 일부분만 사용하는 경우

Interface (Optional Properties)

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.clor) {
      // Error: Property 'clor' does not exist on type 'SquareConfig'
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

객체 프로퍼티의 일부분만 사용하는 경우

Interface (Readonly Properties)

interface Point {
    readonly x: number;
    readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 오류!

객체가 생성 된 이후 수정이 불가능함

Interface (Readonly Properties)

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;

ro[0] = 12; // 오류!
ro.push(5); // 오류!
ro.length = 100; // 오류!
a = ro; // 오류!

모든 변경 메서드가 제거된 ReadonlyArray<T>

초과 프로퍼티 검사

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

// 오류 발생
let mySquare = createSquare({ colour: "red", width: 100 });


// 타입 단언을 통해 통과가능
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

초과 프로퍼티 검사

// 인덱스 서명
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

// 객체를 다른 변수에 할당하는 것도 방법
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);

문자열 인덱스 서명을 추가하는 것도 방법

초과 프로퍼티 검사 문제는 대부분 오류니 잘 확인해야함...

Interface (Fuction type)

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result > -1;
}

// 타입추론
let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

프로퍼티로 객체를 기술하는 것 이외에도 함수 타입을 정의할 수 있다

💁‍♂️ 제네릭

Generic

// number에 한정적이다
function echo(arg: number): number {
  console.log(arg)  
  return arg;
}

// 반환 타입에 대한 정보를 알 수 없다
function echo(arg: any): any {
  console.log(arg)    
  return arg;
}

단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성가능하다

제네릭이 없다면 특정 타입을 명시해야 함

Generic

function echo<T>(arg: T): T {
    return arg;
}

let output = echo<string>("myString");

// 타입추론
// 복잡한 경우 컴파일러가 유추 불가능, 명시 필요
let output = echo("myString");

단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성가능하다

컴파일러는 'myString' 을 보고 타입으로 T를 결정

Generic Types

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity1: <T>(arg: T) => T = identity;

// 다른 이름으로도 사용 가능
let myIdentity2: <K>(arg: K) => K = identity;

// 객체 리터럴 타입으로 선언 가능
let myIdentity3: { <T>(arg: T): T } = identity;

함수 자체 타입과 제네릭 인터페이스

Generic Types

interface GenericIdentityFn {
  <T>(arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

// 인터페이스로 지정
let myIdentity: GenericIdentityFn = identity;

함수 자체 타입과 제네릭 인터페이스

객체 리터럴을  인터페이스로 가져올 수 있음

Generic 타입 조건

extends 키워드를 사용하여 제약조건을 추가한다

interface MyType<T extends string | number> {
  name: string,
  value: T
}

const dataA: MyType<string> = {
  name: 'Data A',
  value: 'Hello world'
};

// TS2344: Type 'boolean' does not satisfy the constraint 'string | number'.
const dataB: MyType<boolen> = {
  name: 'Data B',
  value: true
};

💁‍♂️ Enums

Numeric enums

enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}

// 초기화 하지 않아도 됨
// 값이 중요하지 않고 단순히 구별돼야 하는 경우 유용
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

값이 숫자로 이뤄짐

String enums

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

값이 문자로 이뤄짐

값이 중요하지 않더라도 유의미한 정보를 제공해주는게 디버깅에 용이할듯

Heterogeneous enums

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

값을 문자와 숫자를 섞어서 사용

런타임에서 열거형

enum E {
    X, Y, Z
}

function f(obj: { X: number }) {
    return obj.X;
}


f(E);

런타임에 존재하는 실제 객체이다

"use strict";
var E;
(function (E) {
    E[E["X"] = 0] = "X";
    E[E["Y"] = 1] = "Y";
    E[E["Z"] = 2] = "Z";
})(E || (E = {}));

JS 변환 코드

역 매핑

숫자 열거형은 역 매핑을 받는다.

JS 변환 코드

enum Enum {
    X, Y, Z
}

function f(obj: { X: number }) {
    return obj.X;
}


let x = Enum.X;
let nameOfX = Enum[x]; // "X"
"use strict";
var E;
(function (E) {
    E[E["X"] = 0] = "X";
    E[E["Y"] = 1] = "Y";
    E[E["Z"] = 2] = "Z";
})(E || (E = {}));

정방향 (name -> value) 과 역방향 (value -> name) 모두 가능

런타임에서 열거형

enum E {
    X = 'x',
    Y = 'y',
    Z = 'z'
}

function f(obj: { X: string }) {
    return obj.X;
}


f(E);

런타임에 존재하는 실제 객체이다

"use strict";
var E;
(function (E) {
    E["X"] = "x";
    E["Y"] = "y";
    E["Z"] = "z";
})(E || (E = {}));

JS 변환 코드

Const enums

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [
  Directions.Up, 
  Directions.Down, 
  Directions.Left, 
  Directions.Right
]

const 열거형은 사용공간에 인라인 된다

"use strict";
let directions = [
    0 /* Up */,
    1 /* Down */,
    2 /* Left */,
    3 /* Right */
];

JS 변환 코드

💁‍♂️ 데코레이터

Class Decorator

클래스 선언 직전에 선언, 클래스의 생성자를 인수로 전달 받음

@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

Method Decorator

메서드 선언 직전에 선언

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable
    greet() {
        return "Hello, " + this.greeting;
    }
}

// 정적 맴버에 대한 클래스 생성자 함수, 멤버 이름, 프로퍼티 설명자
function enumerable (
	target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = true;
};

Property Decorator

프로퍼티 선언 직전에 선언

class Greeter {
    @writable
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return "Hello, " + this.greeting;
    }
}

function writable (target: any, propertyKey: string) {
    // description 역할
    return {
      writable: true
    }
};

Parameter Decorator

매개변수 선언 직전에 선언

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    greet(@pringInfo name: string) {
        return "Hello, " + name + this.greeting;
    }
}

function pringInfo (target: any, methodName: string, paramIndex: number) {
    console.log(target) // 클래스 프로토타입, {constructor: f, greet: f}
    console.log(methodName) // 아름, greet
    console.log(paramIndex) // 매개변수 index, 0
};

Decorator Factories

데코레이터 선언에 적용되는 방식을 변경하는 방법

function color(value: string) { 
    return function (target) { 
        // 'target'과 'value' 변수를 가지고 무언가를 수행합니다.
    }
}

단순히 데코레이터가 런타임헤 호출한 표현식을 반환하는 함수

Decorator Composition

데코레이터는 위에서 아래로 평가되며 결과는 아래에서 위로 호출

function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}
f(): evaluated
g(): evaluated
g(): called
f(): called

최적 공통 타입 (Best common type)

여러 표현식에서 타입 추론이 발생할 때, 최적 공통 타입을 계산한다

let x = [0, 1, null];

x = [null]
x = [0, 1, 2]
x = [null, 1 ,2]

// Type 'string' is not assignable to type 'number | null'.
x = ['후보에 없음']

최적 공통 타입이 존재하지 않으면 추론의 결과는 유니언 배열 타입

  • 러닝커브
  • 정적 타이핑으로 인한 코드량
  • d.ts (ts 지원여부)
  • any 타입과 ts-ignore의 난무
  • 프로젝트 규모
  • JS의 장점 (유연함)

 

 

Typescript를 사용해보니..

Made with Slides.com