Typescript FastCampus W03

강의 시작 14:10

RxJS 숙제: https://github.com/typescript-fastcampus/rxjs-for-you

영화 추천 숙제: https://github.com/typescript-fastcampus/recommend-movies-for-me

W02 Q&A

Interface 대신 type 을 사용할때 장점

참고: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types

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

interface SetPoint {
  (x: number, y: number): void;
}
interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}
// primitive
interface x = number; // ㄴㄴ 

type x = number; // ㅇㅇ 가능

// tuple
type x = [number, string];

인터페이스와 달리 유형 별칭은 기본형 및 튜플과 같은 다른 유형에도 사용 가능하고, 인터페이스와 달리 새로 생성하는게 아닌 단순 참조함.

RxJS 질문(?)

데이터 엔드포인트: https://api.androidhive.info/contacts/

 

질문 상세: 위 사이트에 들어가면 모든 객체 내에 gender가 존재합니다.
gender 가 female인 경우에 데이터만 뽑고 싶어서 filter 안에서 for of 문을 만들고, 이 안이 if 조건문을 넣었는데요,
filter의 반환값이 무조건 boolean이어야 해서 if (female == gender) return true; else return false; 로 두니
for of 문이 끝나버려서 전체 비교가 불가했습니다.
filter 안에서 루프문이 들어가고 루프문 안에 조건을 비교하여 필터된 값을 뿌려야하는 경우? 에는 어떻게 해야 할까요?

Github 링크: https://github.com/typescript-fastcampus/rxjs-tips 

제한 시간 15분

데이터 엔드포인트: https://api.androidhive.info/contacts/

 

female 로 필터링된 연락처 중, 같은 주소에 사는 연락처의 이름만 출력해주세요.

Github 링크: https://github.com/typescript-fastcampus/rxjs-tips 

제한 시간 20분

contact [ 'Angelina Jolie', 'Dido', 'Adele', 'Kate Winslet' ]
contact [ 'Hax0r', 'Kim' ]
contact [ 'Woo', 'Lee' ]
contact [ 'Kim' ]

Using {groupBy, mergeMap, Pluck, toArray} operators

const contacts$ = (gender: string) => {
  return from(axios.get('https://api.androidhive.info/contacts/')).pipe(
    map((response: AxiosResponse) => [
      ...response.data.contacts,
      ...dummyData
    ] as contact[]),
    // 시퀀스 병합
    concatAll(),
    // 필터
    filter(contact => contact.gender == gender),
    groupBy(person => person.address),
    // 다시 묶어서 pluck
    mergeMap(group => group.pipe(pluck('name'), toArray())),
  );
};

contacts$('female').subscribe((contact: string[]) => {
  console.log('contact', contact);
});

Type definition 는 devdependencie 로 ! (--save-dev)

어떤 라이브러리가 프로젝트의 컴파일 타임에만 필요하면 devDependencies에 넣고, 런타임에도 계속 쓰이는 것이면 dependencies 에 넣어야 합니다.

var 대신 let

var name = 'hax0r';
function getName() {
  console.log(name);
  var name = 'youngjun';
  console.log(name);
}

// undefined
// youngjun

var 키워드를 사용할 경우, 변수 Hoisting 현상이 발생함

Hoisting이란 var 키워드를 사용하여 변수를 선언 시, 해당 변수가 속한 범위(scope) 최상단으로 올려버리는 현상을 일컽는다.

console.log(hax0rName);

// Uncaught ReferenceError: name is not defined at <anonymous>:1:13

console.log(hax0rName);
var hax0rName;

// undefined

영화를 부탁해

https://github.com/typescript-fastcampus/recommend-movies-for-me

영화를 부탁해 V2

https://github.com/typescript-fastcampus/recommend-movies-for-me

- TypeORM
- Serverless

+ 다음 영화
+ 영화 마다 추천 수 높은 평점 한개 이상 출력

TypeORM

ORM (Object Relational Mapping)

Entity 간 서로 관계를 맵핑.

import {Column, PrimaryGeneratedColumn, Entity} from "typeorm";

@Entity()
export class Category {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;
}

Serverless

https://serverless.com/

RxJS 숙제

# Filter out close points

## Goal
- Create a function that returns an UnaryFunction that filters out points closer to the given distance from the last output point.
- You can change or add codes between `>>>>>>` ~ `<<<<<<`

## Example
when `distance: 2.1`
source: {x: 1, y: 1} --> {x: 1, y: 1} --> {x: 2, y: 3} --> {x: 3, y: 3} --> {x: 4, y: 4}
output: {x: 1, y: 1} -------------------> {x: 2, y: 3} -------------------> {x: 4, y: 4}

## Test
`$npm run start`

답안: https://github.com/typescript-fastcampus/rxjs-for-you

질문 왕

컴파일 옵션 알아보기

tsc --init

@types

TS 2.0부터 사용 가능해진 내장 type definition 시스템

기본적으로 node_modules/@types 참고함.

type definition이 현재 사용중인 버전과 환경에 안맞을 경우 커스텀할 수 도 있다는 장점, definition 이 없는 경우 직접 만들어서 지정할 수 있음.

compileOptions

  • target

  • lib

  • module

  • path

  • declaration

  • log​

  • JS

  • Extra

target

compile 시, 어떤 버전의 JS 스펙을 적용 할지 (e.g. es3, es5, es6 ...)

기본 값 es3

lib

기본 type definition 라이브러리 사용 여부
lib: "[]" 빈 배열로 지정 시 아무것도 안가지고옴.

  • target es3일 때, 기본 값 lib.d.ts
  • target es5일 때, 기본 값 DOM,ES5,ScriptHost
  • target es6 일 때, DOM,ES6,DOM.Iterable,ScriptHost

Module

컴파일 된 모듈의 결과물을 어떤 모듈 시스템으로 할지 결정 (commonJS, systemJs)

moduleResoliution

ts 소스에서 모듈을 사용하는 방식 지정 (Classic or Node)

path

baseUrl, paths

가져올때 사용하는 것, 상대경로 방식이 아닌 baseUrl로 꼭지점과 paths 안의 키/밸류로 모듈을 가져가는 방식

outDir, outPath

컴파일 시, 출력할 파일들의 경로 설정.
outPath 의 경우 단일 파일로 출력 시, 파일명

declaration

말 그대로 declaration 파일 (*.d.ts) 생성 여부

declarationDir

declaration 파일 생성 루트

Log

listEmittedFiles: 컴파일된 결과 파일들 이름 출력

pretty: 에러메세지를 예쁘게 띄워 줌

traceResolution: 모듈 검색에 대한 로그 내용 출력 

JS

allowJs: 자바스크립트 파일 컴파일 허용

checkJs: js파일 모듈을 사용시 js파일의 오류 검사 (allowjs 가 참일 때)

maxNodeModuleJsDepth: JS 모듈 검색 depth

  • removeComments
    • 주석 삭제
  • noImplicitReturns
    • 함수의 모든 경로가 값을 반환하지 있지  않을 경우 에러 발생
  • noUnusedLocals
    • 사용안된 지역 변수에 대한 오류 보고
  • noUnusedParameters
    • 사용안된 파라미터에 대한 오류 보고
  • sourceMap
    • For debugging
  • noImplicitAny
    • Any 타입 금지
  • allowUnreachableCode
    • 도달 불가능한 코드에 대한 허용
  • extendedDiagnostics
    • 컴파일 상세 정보 응답

데코레이터

데코레이터는 클래스 선언, 방법, 접근자, 속성 또는 매개변수에 부착할 수 있는 특별한 종류의 선언 = 필요에 따른 장식

데코레이터는 @expression 형식을 사용하는데, expression은 데코레이팅 된 선언에 대한 정보와 함께 존재하며 이는 런타임에 호출됨.

데코레이터 타입스크립트만의 스펙 ㄴㄴECMAScript 표준의 proposal 중 하나.

타입스크립트는 뭐다? 자바스크립트의 상위 집합이다.

어떤 형태로 사용되나 ?

// cats.controller.ts

import { Controller, Get, Post, Param } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }

  @Get(':id')
  findById(@Param('id') id: number): string {
    return 'test dd';
  }
}

tsc --init

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

experimentalDecorators 가 참이어야 데코레이터 사용 가능.

  • Class Decorator

  • Method Decorator

  • Property Decorator

  • Parameter Decorator

Class Decorator

function SelfDriving(constructorFunction: Function) {
    console.log('-- decorator function invoked --');
    constructorFunction.prototype.selfDrivable = true;
}

@SelfDriving
class Car {
    private _make: string;
    constructor(make: string) {
        console.log('-- this constructor invoked --');
        this._make = make;
    }
}
console.log('-- creating an instance --');
let car: Car = new Car("Nissan");
console.log(car);
console.log(`selfDriving: ${car['selfDrivable']}`);

console.log('-- creating one more instance --');
car = new Car("Toyota");
console.log(car);
console.log(`selfDriving: ${car['selfDrivable']}`);
-- decorator function invoked --
-- creating an instance --
-- this constructor invoked --
Car { _make: 'Nissan' }
selfDriving: true
-- creating one more instance --
-- this constructor invoked --
Car { _make: 'Toyota' }
selfDriving: true

파라미터도 함께 정의할 수  있음.

function Wheels(numOfWheels: number) {
  console.log('-- decorator factory invoked --');
  return function (constructor: Function) {
    console.log('-- decorator invoked --');
    constructor.prototype.wheels = numOfWheels;
  }
}

@Wheels(4)
class Vechical {
  private _make: string;
  constructor(make: string) {
    console.log('-- this constructor invoked --');
    this._make = make;
  }
}

console.log('-- creating an instance --');

let vechical: Vechical = new Vechical("Nissan");
console.log(vechical);

console.log(vechical['wheels']);

console.log('-- creating another instance --');

vechical = new Vechical("Toyota");
console.log(vechical);
console.log(vechical['wheels']);

Method Decorator

function Enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log("-- target --");
  console.log(target);
  console.log("-- proertyKey --");
  console.log(propertyKey);
  console.log("-- descriptor --");
  console.log(descriptor);

  descriptor.enumerable = true;
}

function Enumerable2(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  }
}

class A {
  @Enumerable
  @Enumerable2(true)
  run() {
    console.log("inside run method...");
  }
}
console.log("-- creating instance --");
const a = new A();
console.log("-- looping --");
for (let key in a) {
  console.log("key: " + key);
}
  • target : 해당 field를 가지고 있는 Class의 prototype
  • propertyKey : 해당 field의 이름
  • descriptor : 새로 정의하고자 하는 속성에 대한 설명
function Enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log("-- target --");
  console.log(target);
  console.log("-- proertyKey --");
  console.log(propertyKey);
  console.log("-- descriptor --");
  console.log(descriptor);
  //make the method enumerable
  descriptor.enumerable = true;
}
interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}

Property Decorator

function notNull(target: any, propertyKey: string) {
    Validator.registerNotNull(target, propertyKey);
}

class Validator {
    private static notNullValidatorMap: Map<any, string[]> = new Map();

    //todo add more validator maps

    static registerNotNull(target: any, property: any): void {
        let keys: string[] = this.notNullValidatorMap.get(target);
        if (!keys) {
            keys = [];
            this.notNullValidatorMap.set(target, keys);
        }
        keys.push(property);
    }

    static validate(target: any): boolean {
        let notNullProps: string[] = this.notNullValidatorMap.get(Object.getPrototypeOf(target));
        if (!notNullProps) {
            return true;
        }
        let hasErrors: boolean = false;
        for (const property of notNullProps) {
            let value = target[property];
            if (!value) {
                console.error(property + " value cannot be null");
                hasErrors = true;
            }
        }
        return hasErrors;
    }
}

class Person {
    @notNull
    name: string;

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

console.log("-- creating instance --");
let person: Person = new Person(null);
console.log(person);
let b = Validator.validate(person);
console.log("validation passed: " + !b);
console.log("-- creating another instance --");
let person2: Person = new Person("Tina");
console.log(person2);
b = Validator.validate(person2);
console.log("validation passed: " + !b);

Parameter Decorator

파라미터 선언 직전에 파라미터 데코레이터를 선언, parameterIndex 를 인자 값으로 받음

class MyClass {
  myMethod(@logParam myParameter: string) {}
}

function logParam(target: any, propertyKey: string, parameterIndex: number) {
  target.test = propertyKey;
  console.log(target);
  console.log('propertyKey', propertyKey);
  console.log('parameterIndex', parameterIndex);
}

console.log(MyClass.prototype["test"]); // myParameter

실제 환경에서 쓰일만한 것들은 이미 잘 만들어져있다.

npm i core-decorators

제너릭

Class 내부에서 사용할 데이터의 타입을 외부에서 지정을 하는 기법.

정적 타입의 언어의 경우, 함수 및 클래스를 선언하는 시점에서 매개변수 혹은 리턴 타입을 정의해야하기 때문에 기본적으로는 특정 타입을 위해 만들어진 클래스나 함수를 다른 타입을 위해 재 사용할 수가 없다.

그래서 고안된게 제너릭이다. (함수와 클래스의 범용적인 사용이 가능케한다.)

타입스크립트에서만 국한된 내용이 아니다. 정적 타입의 언어의 경우 대부분의 경우에서 다 사용하고 있다.

스택 자료 구조형을 예제로 들어보자.

class Stack {
  private data: any[] = [];

  contructor() {}

  push(item: any): void {
    this.data.push(item);
  }

  pop(): any {
    return this.data.pop();
  }
}

const stack = new Stack();
stack.push(1);
stack.push('string');

console.log(stack.pop().substring(0)); // string
console.log(stack.pop().substring(0)); // TypeError: stack.pop(...).substring is not a function


대게 스택 같은 자료구조는 범용적인 타입을 수용할 수 있도록 되있기에 typescript 에서는 any 를 통해 구현 가능하다.

Any ㄴㄴ
추론 불가 + 런타임 에러 발생

그렇다면 어떻게 해결할 수 있을까 ?

해당 클래스를 상속 받아서 처리하자

class NumberStack extends Stack {
  constructor() {
    super();
  }

  push(item: number): void {
    super.push(item);
  }

  pop(): number {
    return super.pop();
  }
}

위와 같은 형태면 자료형(타입) 과 1:1 대응하기에 타입이 늘어나면 클래스도 늘어남. 너무 안좋은 구조임

제너릭을 쓰자.

class Stack<T> {
  private data: T[] = [];

  constructor() {}

  push(item: T): void {
    this.data.push(item);
  }

  pop(): T {
    return this.data.pop();
  }
}

클래스 식별자 선언부에 <T> 는 제너릭을 사용하겠다는 뜻 이다. 여기서 T는 Type의 약자다.

관용적으로 사용되는거고 (rxjs 에서 stream 에 $ 심볼과 유사 and for 문에서 i 변수랑 유사), 꺽쇠안에 다른 식별자로 대체 가능한 내용을 넣을 수 있다.

제네릭을 사용하겠다고 선언한 경우, 이제 앞으로 T 는 해당 클래스에서 사용할 수 있는 타입 변수(Type variables)다.

class Stack<T> {
  private data: T[] = [];

  constructor() {}

  push(item: T): void {
    this.data.push(item);
  }

  pop(): T | undefined {
    return this.data.pop();
  }
}

const numberStack = new Stack<number>();
const stringStack = new Stack<string>();

numberStack.push(1);
stringStack.push('string');

선언한 타입만을 저장하고 반환하며, 비로소 컴파일러가 타입 추론을 할 수 있게되었다.

다중으로도 가능함

class Stack<T, B> {
  private data: T[] = [];

  constructor() {}

  push(item: T): void {
    this.data.push(item);
  }

  pop(): T | undefined {
    return this.data.pop();
  }
}

const numberStack = new Stack<number, string>();
const stringStack = new Stack<string, number>();

numberStack.push(1);
stringStack.push('string');

모듈

모듈은 구현 세부 사항을 캡슐화하고 공개 API를 노출해 다른 코드에서 쉽게 로드하고 사용할 수 있도록 재사용 가능한 코드 조각

일전에 말했듯이 이전에 작고 단순한 프로그램에서 현재는 크고 복잡한 형태로 진화했음.
따라서 우리에게는 모듈이 절실했음.

모듈은 의존성을 모듈 로더(module loader)를 통해 처리됨 (e.g. CommonJS, RequireJs)

모듈화

  • 전역 스코프 분리
  • 캡슐화
  • 재사용성
  • 의존성 관리

네임스페이스

인터페이스

interface 를 통해 값이 특정한 형태를 갖도록 제약한다.

인터페이스의 속성을 읽기 전용 속성 또는 선택 속성으로 정의가 가능하다.

interface Account {
  readonly id: number;
  name: string;
  age?: number;
}

함수형 인터페이스

interface findAccountById {
  (account: AccountEntity, bindDefaultAge: boolean): AccountEntity
}

interface AccountEntity {
  readonly id: number;
  name: string;
  age?: number;
}

const dummyUser: AccountEntity = {
  id: 1,
  name: 'Hax0r',
  age: 25
};

const bindDefaultAge = true;
const findAccount: findAccountById = (user, bindDefaultAge) => dummyUser;
console.log(findAccount(dummyUser, bindDefaultAge));

함수형 인터페이스를 정의하기 위해 호출 시그니쳐를 제공해야하는데 인자값에 대한 타입과 반환 타입을 명시하면된다.

제너릭 인터페이스

interface Command<T, R> {
  id: T;
  run(): R;
}

let c: Command<string, number> = {
  id: Math.random().toString(36),
  run: () => 3
};

console.log(c.id);
console.log(c.run());

본인이 정의한 인터페이스 이름 옆에 <타입 변수>를 붙여 제너릭 인터페이스(generic interface)를 정의할 수 있다.

하이브리드 타입

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;

타입스크립트 공식 예제 참고: https://www.typescriptlang.org/docs/handbook/interfaces.html

호출 가능한 프로퍼티를 갖는 동시에 이외 여러 속성을 갖는 객체가 있다. 이와 같은 타입을 정의하기 위해 호출 시그니쳐와 속성 타입을 동시에 정의할 수 있다.

Counter 타입은 호출 가능한 함수이며 동시에 기본적으로 정의된 프로퍼티를 갖는다. (interval, reset)

색인 시그니쳐

색인 시그니쳐를 통해 색인 가능한 객체의 타입을 정의할 수 있다. 색인에 접근할 때는 대괄호를 이용해 객체의 index signature(id)를 기재하면 된다. (물론 색인에도 타입을 정의할 수 있다)

interface TypescriptMembers {
  [userName: string]: number | undefined;
}


const members: TypescriptMembers = {
  '영준': 24,
  'Hax0r': 25,
};

console.log(members['영준'], members['영준'] === undefined, members['영준2'] === undefined);

확장

중복된 프로퍼티를 갖는 경우 우리는 중복성을 제거하기 위해 "상속"을 일반적으로 사용해서 해결한다. 이 경우 클래스와 동일하게 extends 키워드를 통해 확장할 수 있다.

interface worker {
  readonly companyId: number;
  money: string;
}
interface developer {
  code: string;
}

interface designer {
  painting: string;
}

interface designerDeveloper extends worker, developer, designer {
  run(): void
}

클래스

인터페이스와 클래스의 관계

영화를 부탁해 V2

Typescript FastCampus W03

By hax0r

Typescript FastCampus W03

  • 61