TypeScript 2nd

2woongjae@gmail.com

TypeScript 2nd

2woongjae@gmail.com

Chapter 5-2. var, let, const

var VS let, const

  • var
    • ES5
    • 변수의 유효 범위 : 함수 스코프
    • 호이스팅 (O)
    • 재선언 가능
  • let, const
    • ES6
    • 변수의 유효 범위 : 블록 스코프 (친숙)
    • 호이스팅 (X)
    • 재선언 불가
  • var 말고 let, const

Hoisting

console.log(hoisted_var);

var hoisted_var = '변수를 아래서 선언했는데 사용이 위에서 가능';

////////////////////////////////////////

console.log(hoisted_let);

let hoisted_let = '변수를 아래서 선언했는데 사용이 위에서 불가';

redeclare

var redeclare_var: string = '한번 선언 했는데';
var redeclare_var: string = '또 선언이 가능';
// var redeclare_var: number = 0; (X)

////////////////////////////////////////

let redeclare_let = '한번 선언 했기 때문에';
let redeclare_let = '또 선언이 불가';

/*

그렇지만 var 에서 재선언 하더라도 같은 타입이어야 함.

*/

let 과 const 의 타입 추론

let a: string = '에이';
let b = '비이';

const c: string = '씨이';
const d = '디이';

/*

1. a 는 명시적으로 지정된 타입인 string
2. b 는 타입추론에 의한 타입인 string
3. c 는 명시적으로 지정된 타입인 string
4. d 는 타입추론에 의한 타입인 리터럴타입 "디이"

*/

Chapter 5-3. Type assersions

타입 어설션

  • 적당한 번역을 찾을 수가 없었습니다.
    • Type assertions
  • 형변환과는 다릅니다.
    • 형변환은 실제 데이터 구조를 바꿔줍니다.
  • '타입이 이것이다' 라고 컴파일러에게 알려주는 것을 의미합니다.
    • 그래서 행동에 대해서 작성자가 100% 신뢰하는 것이 중요합니다.
  • 문법적으로는 두가지 방법이 있습니다.
    • 변수 as 강제할타입
    • <강제할타입>변수

sample code

let someValue: any = "this is a string";

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

/*

1. 주로 넓은 타입에서 좁은 타입으로 강제하는 경우가 많다.
2. jsx 에서는 as 를 쓴다.

*/

Chapter 6-1. Type alias

타입 별칭 (별명)

  • 인터페이스랑 비슷해 보입니다.

  • Primitive, Union Type, Tuple

  • 기타 직접 작성해야하는 타입을 다른 이름을 지정할 수 있습니다.

  • 만들어진 타입의 refer 로 사용하는 것이지 타입을 만드는것은 아닙니다.

Aliasing Primitive

type MyStringType = string;

const str = 'world';

let myStr: MyStringType = 'hello';
myStr = str;

/*

별 의미가 없다..

*/

Aliasing Union Type

let person: string | number = 0;
person = 'Mark';

type StringOrNumber = string | number;

let another: StringOrNumber = 0;
another = 'Anna';

/*

1. 유니온 타입은 A 도 가능하고 B 도 가능한 타입
2. 길게 쓰는걸 짧게

*/

Aliasing Tuple

let person: [string, number] = ['Mark', 35];

type PersonTuple = [string, number];

let another: PersonTuple = ['Anna', 24];

/*

1. 튜플 타입에 별칭을 줘서 여러군데서 사용할 수 있게 한다.

*/

Quiz

  • Type Alias 로 Generic 표현하기
  • Type Alias 와 keyof 키워드 사용하기
    • https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoE8uRXG51003heNA0EATIxN

Interface 와 차이점

  • Type Alias 
  • Type Alias 와 keyof 키워드 사용하기
    • https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoE8uRXG51003heNA0EATIxN

Interface 와의 차이점 (1)

type Alias = { num: number }

interface Interface {
    num: number;
}

declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;

/*

1. type alias 는 object literal type 로
2. interface 는 interface 로

*/

Interface 와의 차이점 (2)

type PersonAlias = {
    name: string;
    age: number;
};

interface IPerson extends PersonAlias {

}

let ip: IPerson = {
    name: 'Mark',
    age: 35
};

class PersonImpl implements PersonAlias {
    name: string;
    age: number;

    hello() {
        console.log('안녕하세요');
    }
}

let pi: PersonImpl = new PersonImpl();
pi.hello();

class PersonChild extends PersonAlias {

}

/*

1. 당연한건 type alias 끼리는 extends, implements 불가
2. interface extends type alias 가능
3. class implements type alias 가능
4. class extends type alias 블가 (interface 도 마찬가지)
5. 마치 interface 처럼 동작한다.

*/

Chapter 6-2. Interface

interface - basic

function hello(person: { name: string; age: number; }): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p: { name: string; age: number; } = {
    name: 'Mark',
    age: 35
};

hello(p); // 안녕하세요! Mark 입니다.

///////////////////////////////////////////////////////////////

interface Person {
    name: string;
    age: number;
}

function hello(person: Person): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p: Person = {
    name: 'Mark',
    age: 35
};

hello(p); // 안녕하세요! Mark 입니다.

interface - optional property (1)

interface Person {
    name: string;
    age?: number;
}

function hello(person: Person): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p1: Person = {
    name: 'Mark',
    age: 35
};

const p2: Person = {
    name: 'Anna'
};

hello(p1); // 안녕하세요! Mark 입니다.
hello(p2); // 안녕하세요! Anna 입니다.

interface - optional property (2)

interface Person {
    name: string;
    age?: number;
    [props: string]: any;
}

function hello(person: Person): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p1: Person = {
    name: 'Mark',
    age: 35,
};

const p2: Person = {
    name: 'Anna',
    systers: [
        'Sung',
        'Chan'
    ]
};

const p3: Person = {
    name: 'Bokdaengi',
    father: p1,
    mother: p2
};

hello(p1); // 안녕하세요! Mark 입니다.
hello(p2); // 안녕하세요! Anna 입니다.
hello(p3); // 안녕하세요! Bokdaengi 입니다.

interface - function in interface

interface Person {
    name: string;
    age: number;
    hello(): void;
}

const p1: Person = {
    name: 'Mark',
    age: 35,
    hello: function (): void {
        console.log(this);
        console.log(`안녕하세요! ${this.name} 입니다.`);
    }
};

const p2: Person = {
    name: 'Mark',
    age: 35,
    hello(): void {
        console.log(this);
        console.log(`안녕하세요! ${this.name} 입니다.`);
    }
};

const p3: Person = {
    name: 'Mark',
    age: 35,
    hello: (): void => {
        console.log(this);
        console.log(`안녕하세요! ${this.name} 입니다.`);
    }
};

p1.hello(); // 안녕하세요! Mark 입니다.
p2.hello(); // 안녕하세요! Mark 입니다.
p3.hello(); // 안녕하세요! 입니다.

class implements interface

interface IPerson {
    name: string;
    age?: number;
    hello(): void;
}

class Person implements IPerson {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    hello(): void {
        console.log(`안녕하세요! ${this.name} 입니다.`);
    }
}

const person = new Person('Mark');
person.hello(); // 안녕하세요! Mark 입니다.

interface extends interface

interface Person {
    name: string;
    age?: number;
}

interface Korean extends Person {
    city: string;
}

const k: Korean = {
    name: '이웅재',
    city: '서울'
};

function interface

interface HelloPerson {
    // (name: string, age: number): void;
    (name: string, age?: number): void;
}

let helloPerson: HelloPerson = function (name: string) {
    console.log(`안녕하세요! ${name} 입니다.`);
};

helloPerson('Mark'); // 안녕하세요! Mark 입니다.

/*

함수의 타입 체크는 할당할때가 아니라 사용할때 한다는 점을 명심

*/

Chapter 6-3. Indexable Types

string OR number

interface StringArray {
    [index: number]: string;
}

const sa: StringArray = {}; // 옵셔널하다
sa[100] = '백';

interface StringDictionary {
    [index: string]: string;
}

const sd: StringDictionary = {}; // 옵셔널하다
sd.hundred = '백';

interface StringArrayDictionary {
    [index: number]: string;
    [index: string]: string;
}

const sad: StringArrayDictionary = {};
// 당연히 옵셔널하다.
sad[100] = '백';
sad.hundred = '백';

string index = optional property

interface StringDictionary {
    [index: string]: string;
    name: string;
}

const sd: StringDictionary = {
    name: '이름' // 필수
};

sd.any = 'any'; // 어떤 프로퍼티도 가능

////////////////////////////////////////////////

interface StringDictionaryNo {
    [index: string]: string;
    // name: number; // (X) 인덱서블 타입이 string 값을 가지기 때문에 number 를 필수로 끌어오면 에러
}

Chapter 7. Class

클래스 만들기

class Person {
    name: string;
    age: number;
}

const person: Person = new Person();
console.log(person); // Person {}
person.age = 35;
console.log(person.name); // undefined

/*

1. 생성자 함수가 없으면, 디폴트 생성자가 불린다.

2. 클래스의 프로퍼티 혹은 멤버 변수가 정의되어 있지만, 값을 대입하지 않으면 undefined 이다.
=> 오브젝트에 프로퍼티가 아예 존재하지 않는다.

3. 접근제어자 (Access Modifier) 는 public 이 디폴트 이다.

*/

클래스와 프로퍼티 (1)

class Person {
    name: string;
    age: number;

    constructor() {
        console.log(this.name === null); // false
        console.log(this.name === undefined); // true
    }
}

const person: Person = new Person();
person.name = 'Mark';
person.age = 35;
console.log(person); // Person {name: 'mark', age: 35}

클래스와 프로퍼티 (2)

class Person {
    name: string = 'Mark';
    age: number = 35;

    constructor() {
        console.log(this.name); // 'mark'
    }
}

const person: Person = new Person();
console.log(person); // Person {name: 'Mark', age: 35}

/*

1. 클래스의 프로퍼티를 선언과 동시에 값을 할당하는 방법도 있다.

2. 생성자가 불리기 전에 이미 프로퍼티의 값이 저장되어 있음을 알 수 있다.

*/

클래스와 프로퍼티의 접근 제어자 (1)

class Person {
    public name: string;
    private _age: number;

    constructor(age: number) {
        this._age = age;
    }
}

const person: Person = new Person(35);
person.name = 'Mark';
// person._age (X)
console.log(person); // Person {name: 'Mark', _age: 35}

/*

1. private 으로 설정된 프로퍼티는 dot 으로 접근할 수 없다.

2. 클래스 내부에서는 private 프로퍼티를 사용할 수 있다.

3. private 이 붙은 변수나 함수는 _ 를 이름앞에 붙이는데,
이는 문법이 아니라 널리 쓰이는 코딩 컨벤션이다.

*/

클래스와 프로퍼티의 접근 제어자 (2)

class Parent {
    private privateProp: string;
    protected protectedProp: string;

    constructor() {

    }
}

class Child extends Parent {
    constructor() {
        super();

        this.protectedProp = 'protected';
        // this.privateProp = 'private'; // (X)
    }
}

/*

1. 부모에서 private 으로 설정된 프로퍼티는 상속을 받은 자식에서도 접근할 수 없다.

2. 부모에서 protected 로 설정된 프로퍼티는 상속을 받은 자식에서 접근이 가능하다.

3. 상속을 받은 자식 클래스에서 부모 클래스에 this 를 통해 접근하려면, 생성자에서 super(); 를 통해 초기화 해야한다.

*/

클래스와 디폴트 생성자

class Person {
    public name: string;
    private _age: number;

    constructor(age: number) {
        this._age = age;
    }
}

const person: Person = new Person();

/*

1. 디폴트 생성자는 프로그래머가 만든 생성자가 없을 때 사용할 수 있다.
=> 사용자가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.

*/

클래스와 메서드

class Person {
    constructor(private _name: string, private _age: number) { }

    print(): void {
        console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
    }

    printName = (): void => {
        console.log(`이름은 ${this._name} 입니다.`);
    }

    private printAge(): void  {
        console.log(`나이는 ${this._age} 살 입니다.`);
    }
}

const person: Person = new Person('Mark', 35);
person.print(); // 이름은 Mark 이고, 나이는 35 살 입니다.
person.printName(); // 이름은 Mark 입니다.
// person.printAge(); // (X)

/*

1. 클래스 내부에 작성된 메서드는 public 이 디폴트
2. arrow function 으로 작성 가능
3. private 을 이용하면 클래스 외부애서 접근 불가

*/

클래스와 상속 (1)

class Parent {
    constructor(protected _name: string, protected _age: number) { }

    print(): void {
        console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
    }

    printName = (): void => {
        console.log(`이름은 ${this._name} 입니다.`);
    }

    private printAge(): void  {
        console.log(`나이는 ${this._age} 살 입니다.`);
    }
}

class Child extends Parent {
    _name = 'Mark Jr.';
}

// const p: Child = new Child(); // (X)
const p: Child = new Child('', 5);
p.print(); // 이름은 Son 이고, 나이는 5 살 입니다.

/*

1. 상속은 extends 키워드를 이용한다.
2. 자식 클래스에서 디폴트 생성자는 부모의 생성자와 입력 형태가 같다.

*/

클래스와 상속 (2)

class Parent {
    constructor(protected _name: string, private _age: number) { }

    print(): void {
        console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
    }

    protected printName = (): void => {
        console.log(`이름은 ${this._name} 입니다.`);
    }

    protected printAge(): void  {
        console.log(`나이는 ${this._age} 살 입니다.`);
    }
}

class Child extends Parent {
    constructor(age: number) {
        super('Mark Jr.', age);

        this.printName();
        this.printAge();
    }
}

const p: Child = new Child(1);
// 이름은 Son 입니다.
// 나이는 1 살 입니다.

/*

1. 생성자를 정의하고, this 를 사용하려면, super 를 통해 부모의 생성자를 호출해줘야 한다.
2. super 를 호출할때는 부모 생성자의 입력 타입이 같아야 한다.
3. super 를 호출하는 것은 클래스 외부에서 호출하는 것과 같다.
4. protected 함수를 호출해서 그 안의 private 을 출력하는 것에 주의한다.

*/

클래스와 getter, setter

class Person {
    private _name: string;
    private _age: number;
    
    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
     }

    get name() {
        return this._name;
    }

    set name(name: string) {
        // 작업
        this._name = `${name} Lee`;
    }
}

const person: Person = new Person('Mark', 35);

console.log(person.name);
person.name = 'Woongjae';
console.log(person.name);

/*

1. _ 를 변수명 앞에 붙이고, 내부에서만 사용한다.
2. getter 를 함수처럼 설정하면, 프로퍼티처럼 꺼내쓸수있다.
3. 마찬가지로 setter 를 함수처럼 설정하면, 추가 작업을 하고 셋팅할 수 있다.

*/

클래스와 static 프로퍼티 => 클래스 멤버 변수

class Person {
    public static CITY = "";
    private static lastName: string = 'Lee';
    private _name: string;
    private _age: number;
    
    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
     }

    public print() {
        console.log(`${this._name} ${Person.lastName} in ${Person.CITY}.`);
    }
}

const person: Person = new Person('Mark', 35);
Person.CITY = 'Seoul';
person.print(); // Mark Lee in Seoul.

/*

1. static 키워드를 붙힌 프로퍼티는 클래스.프로퍼티로 사용한다.
2. static 프로퍼티에 private, protected 를 붙히면 똑같이 동작한다.

*/

클래스와 static 메서드 => 클래스 멤버 함수

class Person {
    public static Talk(): void {
        console.log('안녕하세요.');
    }
}

Person.Talk(); // 안녕하세요.

모듈에서 private static 프로퍼티 혹은 메서드

class Person {
    private static PROPERTY = '프라이빗 프로퍼티';
    private static METHOD() {
        console.log('프라이빗 메서드');
    }

    constructor() {
        console.log(Person.PROPERTY);
        Person.METHOD();
    }
}

//////////////////////////////////////////////

const PROPERTY = '모듈 내 변수';
function METHOD() {
    console.log('모듈 내 함수');
}

export class Person {
    constructor() {
        console.log(PROPERTY);
        METHOD();
    }
}

Abstract Class

abstract class APerson {
    protected _name: string = 'Mark';
    abstract setName(name: string): void; 
}

class Person extends APerson {
    setName(name: string): void {
        this._name = name;
    }
}

// const person = new APerson(); // (X)
const person = new Person();

/*

1. abstract 키워드가 사용된 클래스는 new 로 생성할 수 없다.
2. abstract 키워드가 사용된 클래스를 상속하면 abstract 키워드가 붙은 함수를 구현해야 한다.

*/

Class 와 private constructor

class Preference {
    private constructor() {

    }
}

// const p: Preference = new Preference(); (X)

/*

1. 생성자 함수 앞에 접근제어자인 private 을 붙일 수 있다.
2. 외부에서 생성이 불가능하다.

*/

Class 와 싱글톤 패턴

class Preference {
    public static getInstance() {
        if (Preference.instance === null) {
            Preference.instance = new Preference();
        }

        return Preference.instance;
    }
    private static instance: Preference = null;
    private constructor() {

    }
}

const p: Preference = Preference.getInstance();

/*

1. private 생성자를 이용해서 내부에서만 인스턴스 생성이 가능하도록 함.
2. pubilc static 메서드를 통해 private static 인스턴스 레퍼런스를 획득한다.
3. Lazy Loading (Initialization) : 최초 실행시가 아니라, 사용시에 할당을 함

*/

Class 와 readonly

class Person {
    private readonly _name: string = null;
    public readonly age: number = 35;

    constructor(name: string) {
        this._name = name;
    }

    public setName(name: string) {
        // this._name = name; (X)
    }
}

const p: Person = new Person('Mark');
console.log(p.age);
// p.age = 36; // (X)

/*

1. private readonly 로 선언된 경우, 생성자에서는 할당이 가능하다.
2. private readonly 로 선언된 경우, 생성자 이외에서는 할당이 불가능하다.
3. public readonly 로 선언된 경우, 클래스 외부에서는 다른값을 할당할 수 없다.
4. 마치 getter 만 있는 경우와 같다.

*/

Exercise (1)

function Car(name) {
    this.name = name;
    this.speed = 0;
 
    this.honk = function() {
        console.log("부우우웅");
    };
 
    this.accelerate = function(speed) {
        this.speed = this.speed + speed;
    }
}

var car = new Car("BENZ");
car.honk();
console.log(car.speed);
car.accelerate(10);
console.log(car.speed);

Exercise (2)

var baseObject = {
    width: 0,
    length: 0
};

var rectangle = Object.create(baseObject);
rectangle.width = 8;
rectangle.length = 6;
rectangle.area = function() {
    return this.width * this.length;
};

console.log(rectangle.area());

Exercise (3)

var person = {
    _firstName: ""
};

// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
// ES5 이상
Object.defineProperty(person, "firstName", {
    get: function () {
        return this._firstName;
    },
    set: function (value) {
        if (value.length > 3) {
            this._firstName = value;
        }
        else {
            this._firstName = "";
        }
    },
    enumerable: true,
    configurable: true
});

console.log(person.firstName);
person.firstName = "Ma";
console.log(person.firstName);
person.firstName = "Maximilian";
console.log(person.firstName);