Lead Software Engineer @ProtoPie
Microsoft MVP
TypeScript Korea User Group Organizer
Marktube (Youtube)
이 웅재
enum ToastType {
    AFTER_SAVED,
    AFTER_PUBLISHED,
    AFTER_RESTORE,
}
interface Toast {
    type: ToastType,
    createdAt: string,
}
const toasts: Toast[] = [...];// toastNodes1 -> (JSX.Element | undefined)[]
const toastNodes1 = toasts.map((toast) => {
  if (toast.type === ToastType.AFTER_SAVED)
    return (
      <div key={toast.createdAt}>
        <AfterSavedToast />
      </div>
    );
  else if (toast.type === ToastType.AFTER_PUBLISHED)
    return (
      <div key={toast.createdAt}>
        <AfterPublishedToast />
      </div>
    );
  else if (toast.type === ToastType.AFTER_RESTORE)
    return (
      <div key={toast.createdAt}>
        <AfterRestoredToast />
      </div>
    );
});// toastNodes2 -> JSX.Element[]
const toastNodes2 = toasts.map((toast) => {
  if (toast.type === ToastType.AFTER_SAVED)
    return (
      <div key={toast.createdAt}>
        <AfterSavedToast />
      </div>
    );
  else if (toast.type === ToastType.AFTER_PUBLISHED)
    return (
      <div key={toast.createdAt}>
        <AfterPublishedToast />
      </div>
    );
  else
    return (
      <div key={toast.createdAt}>
        <AfterRestoredToast />
      </div>
    );
});// toastNodes3 -> JSX.Element[]
const toastNodes3 = toasts.map((toast) => {
  if (toast.type === ToastType.AFTER_SAVED)
    return (
      <div key={toast.createdAt}>
        <AfterSavedToast />
      </div>
    );
  else if (toast.type === ToastType.AFTER_PUBLISHED)
    return (
      <div key={toast.createdAt}>
        <AfterPublishedToast />
      </div>
    );
  else if (toast.type === ToastType.AFTER_RESTORE)
    return (
      <div key={toast.createdAt}>
        <AfterRestoredToast />
      </div>
    );
  else return neverExpected(toast.typs);
});
function neverExpected(value: never): never {
  throw new Error(`Unexpected value : ${value}`);
}// toastNodes4 -> JSX.Element[]
const toastNodes4 = toasts.map((toast) => {
  if (toast.type === ToastType.AFTER_SAVED)
    return (
      <div key={toast.createdAt}>
        <AfterSavedToast />
      </div>
    );
  if (toast.type === ToastType.AFTER_PUBLISHED)
    return (
      <div key={toast.createdAt}>
        <AfterPublishedToast />
      </div>
    );
  if (toast.type === ToastType.AFTER_RESTORE)
    return (
      <div key={toast.createdAt}>
        <AfterRestoredToast />
      </div>
    );
  return neverExpected(toast.typs);
});const toastNodes5 = toasts.map((toast) => {
  switch (toast.type) {
    case ToastType.AFTER_SAVED:
      return (
        <div key={toast.createdAt}>
          <AfterSavedToast />
        </div>
      );
    case ToastType.AFTER_PUBLISHED:
      return (
        <div key={toast.createdAt}>
          <AfterPublishedToast />
        </div>
      );
    case ToastType.AFTER_RESTORE:
      return (
        <div key={toast.createdAt}>
          <AfterRestoredToast />
        </div>
      );
    default:
      return neverExpected(toast.type);
  }
});
// v3.9.7
class Square1 {
  area; // error! implicit any
  sideLength; // error! implicit any
}생성자 함수가 없으면, 디폴트 생성자가 불린다.
클래스의 프로퍼티 혹은 멤버 변수가 정의되어 있지만, 값을 대입하지 않으면 undefined 이다.
접근제어자 (Access Modifier) 는 public 이 디폴트 이다.
class Person1 {
  name!: string;
  age!: number;
}
const person: Person1 = new Person1();
console.log(person); // Person1 {}
person.age = 38;
console.log(person.name); // undefinedclass Person2 {
  name!: string;
  age!: number;
  constructor() {
    console.log(this.name === null); // false
    console.log(this.name === undefined); // true
  }
}
const person2: Person2 = new Person2();
person2.name = 'Mark';
person2.age = 38;
console.log(person2); // Person2 {name: 'Mark', age: 38}클래스의 프로퍼티를 선언과 동시에 값을 할당하는 방법도 있다.
생성자가 불리기 전에 이미 프로퍼티의 값이 저장되어 있음을 알 수 있다.
class Person3 {
  name: string = 'Mark';
  age: number = 38;
  constructor() {
    console.log(this.name); // 'mark'
  }
}
const person3: Person3 = new Person3();
console.log(person3); // Person3 {name: 'Mark', age: 38}private 으로 설정된 프로퍼티는 dot 으로 접근할 수 없다.
클래스 내부에서는 private 프로퍼티를 사용할 수 있다.
private 이 붙은 변수나 함수는 _ 를 이름앞에 붙이는데, 이는 문법이 아니라 널리 쓰이는 코딩 컨벤션이다.
class Person4 {
  public name!: string;
  private _age: number;
  constructor(age: number) {
    this._age = age;
  }
}
const person4: Person4 = new Person4(38);
person4.name = 'Mark';
// person._age (X)
console.log(person4); // Person {name: 'Mark', _age: 38}부모에서 private 으로 설정된 프로퍼티는 상속을 받은 자식에서도 접근할 수 없다.
부모에서 protected 로 설정된 프로퍼티는 상속을 받은 자식에서 접근이 가능하다.
상속을 받은 자식 클래스에서 부모 클래스에 this 를 통해 접근하려면, 생성자에서 super(); 를 통해 초기화 해야한다.
class Parent5 {
  private privateProp!: string;
  protected protectedProp!: string;
  constructor() {}
}
class Child5 extends Parent5 {
  constructor() {
    super();
    this.protectedProp = 'protected';
    // this.privateProp = 'private'; // (X)
  }
}
const child5 = new Child5();
console.log(child5);디폴트 생성자는 프로그래머가 만든 생성자가 없을 때 사용할 수 있다.
사용자가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.
생성자에서 바로 멤버로 할당할 수 있는 방법이 있다.
class Person6 {
  constructor(public name: string, private _age: number) {}
}
const person6: Person6 = new Person6('Mark', 38);
// const person6: Person6 = new Person6();클래스 내부에 작성된 메서드는 public 이 디폴트
arrow function 으로 작성 가능
private 을 이용하면 클래스 외부애서 접근 불가
class Person7 {
  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: Person7 = new Person7('Mark', 36);
person.print(); // 이름은 Mark 이고, 나이는 36 살 입니다.
person.printName(); // 이름은 Mark 입니다.
// person.printAge(); // (X)상속은 extends 키워드를 이용한다.
자식 클래스에서 디폴트 생성자는 부모의 생성자와 입력 형태가 같다.
접근제어자의 오버라이드
class Parent8 {
  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 Child8 extends Parent8 {
  _name = 'Mark Jr.';
}
// const p: Child8 = new Child8(); // (X)
const child8: Child8 = new Child8('', 5);
child8.print(); // 이름은 Mark Jr. 이고, 나이는 5 살 입니다.
console.log(child8._name);생성자를 정의하고, this 를 사용하려면, super 를 통해 부모의 생성자를 호출해줘야 한다.
super 를 호출할때는 부모 생성자의 입력 타입이 같아야 한다.
super 를 호출하는 것은 클래스 외부에서 호출하는 것과 같다.
protected 함수를 호출해서 그 안의 private 을 출력하는 것에 주의한다.
class Parent9 {
  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 Child9 extends Parent9 {
  constructor(age: number) {
    super('Mark Jr.', age);
    this.printName();
    this.printAge();
  }
}
const child9: Child9 = new Child9(1); // 이름은 Mark Jr. 입니다. 나이는 1 살 입니다._ 를 변수명 앞에 붙이고, 내부에서만 사용한다.
getter 를 함수처럼 설정하면, 프로퍼티처럼 꺼내쓸수있다.
마찬가지로 setter 를 함수처럼 설정하면, 추가 작업을 하고 셋팅할 수 있다.
class Person10 {
  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 person10: Person10 = new Person10('Mark', 36);
console.log(person10.name);
person10.name = 'Woongjae';
console.log(person10.name);class Person11 {
  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} ${Person11.lastName} in ${Person11.CITY}.`);
  }
}
const person11: Person11 = new Person11('Mark', 36);
Person11.CITY = 'Seoul';
person11.print(); // Mark Lee in Seoul.class Person12 {
  public static Talk(): void {
    console.log('안녕하세요.');
  }
}
Person12.Talk(); // 안녕하세요./*
class Person13 {
  private static PROPERTY = '프라이빗 프로퍼티';
  private static METHOD() {
    console.log('프라이빗 메서드');
  }
  constructor() {
    console.log(Person13.PROPERTY);
    Person13.METHOD();
  }
}
*/
const PROPERTY = '모듈 내 변수';
function METHOD() {
  console.log('모듈 내 함수');
}
export class Person13 {
  constructor() {
    console.log(PROPERTY);
    METHOD();
  }abstract 키워드가 사용된 클래스는 new 로 생성할 수 없다.
abstract 키워드가 사용된 클래스를 상속하면 abstract 키워드가 붙은 함수를 구현해야 한다.
abstract class AbstractPerson14 {
  protected _name: string = 'Mark';
  abstract setName(name: string): void;
}
class Person14 extends AbstractPerson14 {
  setName(name: string): void {
    this._name = name;
  }
}
// const person14 = new AbstractPerson14(); // (X)
const person14 = new Person14();생성자 함수 앞에 접근제어자인 private 을 붙일 수 있다.
하지만 외부에서 생성이 불가능하다.
class PrivateClass {
  private constructor() {}
}
// const p: PrivateClass = new PrivateClass(); (X)private 생성자를 이용해서 내부에서만 인스턴스 생성이 가능하도록 함.
pubilc static 메서드를 통해 private static 인스턴스 레퍼런스를 획득한다.
Lazy Loading (Initialization) : 최초 실행시가 아니라, 사용시에 할당을 함
/*
class Preference {
  public static getInstance(): Preference {
    if (Preference.instance === null) {
      Preference.instance = new Preference();
    }
    return Preference.instance;
  }
  private static instance: Preference | null = null;
  private constructor() {}
}
const p: Preference = Preference.getInstance();
*/
class PreferenceClass {
  constructor() {}
}
export const Preference = new PreferenceClass();
export type PreferenceType = typeof Preference;private readonly 로 선언된 경우, 생성자에서는 할당이 가능하다.
private readonly 로 선언된 경우, 생성자 이외에서는 할당이 불가능하다.
public readonly 로 선언된 경우, 클래스 외부에서는 다른값을 할당할 수 없다.
마치 getter 만 있는 경우와 같다.
class Person17 {
  private readonly _name: string;
  public readonly age: number = 36;
  constructor(name: string) {
    this._name = name;
  }
  public setName(name: string) {
    // this._name = name; (X)
  }
}
const person17: Person17 = new Person17('Mark');
console.log(person17.age);
// person17.age = 37; // (X)// v3.9.7
class Square2 {
  area: number;
  sideLength: number;
}
const square2 = new Square2();
console.log(square2.area); // compile time - number, runtime - undefined
console.log(square2.sideLength); // compile time - number, runtime - undefined
Class 의 Property 가 생성자 혹은 선언에서 값이 지정되지 않으면,
컴파일 에러를 발생시켜 주의를 준다.
// v3.9.7
class Square2 {
  area: number; // error TS2564: Property 'area' has no initializer and is not definitely assigned in the constructor.
  sideLength: number; // error TS2564: Property 'sideLength' has no initializer and is not definitely assigned in the constructor.
}
// 사용자는 시도조차 할 수 없도록 만듭니다.
const square2 = new Square2();
console.log(square2.area);
console.log(square2.sideLength);
// v3.9.7
class Square3 {
  area: number = 0;
  sideLength: number = 0;
}// v3.9.7
class Square4 {
  area: number;
  sideLength: number;
  constructor(sideLength: number) {
    this.sideLength = sideLength;
    this.area = sideLength ** 2;
  }
}// v4.0.2
class Square5 {
  area; // 4 부터는 any 가 아니라, 생성자에 의해 추론된다.
  sideLength; // 4 부터는 any 가 아니라, 생성자에 의해 추론된다.
  constructor(sideLength: number) {
    this.sideLength = sideLength;
    this.area = sideLength ** 2;
  }
}// v4.0.2
class Square6 {
  sideLength;
  constructor(sideLength: number) {
    if (Math.random()) {
      this.sideLength = sideLength;
    }
  }
  get area() {
    return this.sideLength ** 2; // error! Object is possibly 'undefined'.
  }
}// v4.0.2
class Square7 {
  sideLength!: number; // ! 로 의도를 표현해야 한다.
  constructor(sideLength: number) {
    this.initialize(sideLength);
  }
  initialize(sideLength: number) {
    this.sideLength = sideLength;
  }
  get area() {
    return this.sideLength ** 2;
  }
}hello 의 리턴이 any 이기 때문에 타입 헬퍼가 제대로 되지 않음
helloGeneric 을 사용하면 정상적으로 사용가능
function helloString(message: string): string {
  return message;
}
function helloNumber(message: number): number {
  return message;
}
// 더 많은 반복된 함수들 ...
function hello(message: any): any {
  return message;
}
function helloGeneric<T>(message: T): T {
  return message;
}
console.log(hello('Mark').length);
console.log(hello(38).length); // undefined
console.log(helloGeneric('Mark').length);
// console.log(helloGeneric<number>('Mark').length); (X)
console.log(helloGeneric(38).toString());
// console.log(helloGeneric(36).length); (X)Generic 타입을 쓰지 않으면, T 를 추론
Generic 타입을 쓰면, T 를 검증
function helloBasic<T>(message: T): T {
  return message;
}
console.log(helloBasic<string>('Mark'));
const age = helloBasic(38);
// helloBasic<number>('38'); (X)function helloArray<T>(messages: T[]): T {
  return messages[0];
}
function helloTuple<T, K>(messages: [T, K]): T {
  return messages[0];
}
console.log(helloArray(['Hello', 'World'])); // string[]
console.log(helloArray(['Hello', 1])); // Array<string | number>
console.log(helloTuple(['Hello', 'World'])); // [string, string]
console.log(helloTuple(['Hello', 1])); // [string, number]
// console.log(helloTuple(['Hello', 'world', 1])); // Errortype HelloFunctionGeneric = <T>(message: T) => T;
const helloFunction: HelloFunctionGeneric = <T>(message: T): T => {
  return message;
};
console.log(helloFunction<string>('Hello').length);class Person<T> {
  private _name: T;
  constructor(name: T) {
    this._name = name;
  }
}
new Person('Mark');
// new Person<string>(38); (X)class Person6<T extends string | number> {
  private _name: T;
  constructor(name: T) {
    this._name = name;
  }
}
new Person6('Mark');
new Person6(38);
// new Person6(true); // T 가 string 또는 number 를 상속받기 때문에 boolean 은 Xclass Person7<T, K> {
  private _name: T;
  private _age: K;
  constructor(name: T, age: K) {
    this._name = name;
    this._age = age;
  }
}
new Person7('Mark', 38);interface Person8 {
  name: string;
  age: number;
}
const person8: Person8 = {
  name: 'Mark',
  age: 36
};
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
setProperty(person8, 'name', 'Anna');
// setProperty(person8, 'name', 27);
console.log(getProperty(person8, 'name'));
// console.log(getProperty(person8, 'fullname'));function shuffle(value: string | any[]): string | any[] {
  if (typeof value === 'string')
    return value
      .split('')
      .sort(() => Math.random() - 0.5)
      .join('');
  return value.sort(() => Math.random() - 0.5);
}
console.log(shuffle('Hello, Mark!')); // string | any[]
console.log(shuffle(['Hello', 'Mark', 'long', 'time', 'no', 'see'])); // string | any[]
console.log(shuffle([1, 2, 3, 4, 5])); // string | any[]
function shuffle2<T extends string | any[]>(
  value: T,
): T extends string ? string : T;
function shuffle2(value: any) {
  if (typeof value === 'string')
    return value
      .split('')
      .sort(() => Math.random() - 0.5)
      .join('');
  return value.sort(() => Math.random() - 0.5);
}
// function shuffle2<"Hello, Mark!">(value: "Hello, Mark!"): string
shuffle2('Hello, Mark!');
// function shuffle2<string[]>(value: string[]): string[]
shuffle2(['Hello', 'Mark', 'long', 'time', 'no', 'see']);
// function shuffle2<number[]>(value: number[]): number[]
shuffle2([1, 2, 3, 4, 5]);
// error! Argument of type 'number' is not assignable to parameter of type 'string | any[]'.
shuffle2(1);
function shuffle3(value: string): string;
function shuffle3<T>(value: T[]): T[];
function shuffle3(value: string | any[]): string | any[] {
  if (typeof value === 'string')
    return value
      .split('')
      .sort(() => Math.random() - 0.5)
      .join('');
  return value.sort(() => Math.random() - 0.5);
}
shuffle3('Hello, Mark!');
shuffle3(['Hello', 'Mark', 'long', 'time', 'no', 'see']);
shuffle3([1, 2, 3, 4, 5]);class ExportLibraryModal {
  public openComponentsToLibrary(
    libraryId: string,
    componentIds: string[],
  ): void;
  public openComponentsToLibrary(componentIds: string[]): void;
  public openComponentsToLibrary(
    libraryIdOrComponentIds: string | string[],
    componentIds?: string[],
  ): void {
    if (typeof libraryIdOrComponentIds === 'string') {
      if (componentIds !== undefined) { // 이건 좀 별루지만,
        // 첫번째 시그니처
        libraryIdOrComponentIds;
        componentIds;
      }
    }
    if (componentIds === undefined) { // 이건 좀 별루지만,
      // 두번째 시그니처
      libraryIdOrComponentIds;
    }
  }
}
const modal = new ExportLibraryModal();
modal.openComponentsToLibrary(
  'library-id',
  ['component-id-1', 'component-id-1'],
);
modal.openComponentsToLibrary(['component-id-1', 'component-id-1']);함수다.
컴파일 타임에는 그 함수의 타입만 체크한다.
런타임에 사용 및 처리가 된다.
클래스, 메서드, 프로퍼티, 메서드의 파라미터에 사용할 수 있다.
클래스가 인스턴스로 만들어질 때가 아니라, 최초 클래스가 참조될 때, 한번만 적용된다.
experimentalDecorators 옵션
// Basic
function classDecorator<T extends { new (...args: any[]): {} }>(constructorFn: T) {...}
function classDecoratorFactory(...) {
	return function<T extends { new (...args: any[]): {} }>(constructorFn: T) {...}
}
@classDecorator
class Test {
}
@classDecorator()
class Test {
}
  
/*
# constructorFn
- constructorFn
- return class
*/function classDecorator<T extends { new (...args: any[]): {} }>(target: T) {
  return class extends target {
    constructor(...args: any[]) {
      super(args);
    }
    public print() {
      console.log('this is print');
    }
  };
}
@classDecorator
class Test1 {}
(new Test1() as any).print();function classDecoratorFactory(arg: string) {
  return function<T extends { new (...args: any[]): {} }>(constructorFn: T) {
    constructorFn.prototype.print2 = function() {
      console.log('this is print2', arg);
    };
    constructorFn.prototype.gender = 'male';
    return class extends constructorFn {
      public name = 'mark';
      private _age = 36;
      constructor(...args: any[]) {
        super(args);
      }
      public print() {
        console.log('this is print', arg);
      }
    };
  };
}
@classDecoratorFactory('what')
class Test2 {}
const test2 = new Test2();
(test2 as any).print(); // this is print what
(test2 as any).print2(); // this is print2 what
console.log(Test2.prototype); // class_1 { constructor: [Function: class_1], print: [Function] }
console.log(test2); // class_1 { name: 'mark', _age: 36 }
console.log(Object.keys(test2)); // [ 'name', '_age' ]
console.log((test2 as any).gender); // malefunction methodDecorator(
	target: any,
	propertyKey: string,
	descriptor: PropertyDescriptor
) {...}
function methodDecoratorFactory(...) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {...};
}function methodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log('target', target);
  console.log('propertyKey', propertyKey);
  console.log('descriptor', descriptor);
}
class Test3 {
  @methodDecorator
  public print() {}
}
// target Test3 { print: [Function] }
// propertyKey print
// descriptor { value: [Function], writable: true, enumerable: true, configurable: true }target
클래스
propertyKey
메소드 이름
descriptor
PropertyDescriptor (lib.es5.d.ts)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}
	function methodDecoratorFactory(canBeEdit: boolean = false) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.writable = canBeEdit;
  };
}
class Test4 {
  @methodDecoratorFactory()
  first() {
    console.log('first original');
  }
  @methodDecoratorFactory(true)
  second() {
    console.log('second original');
  }
  @methodDecoratorFactory(false)
  third() {
    console.log('third original');
  }
}
const test4 = new Test4();
test4.first = function() {
  console.log('first new');
}; // runtime error
test4.second = function() {
  console.log('second new');
};// runtime error
test4.third = function() {
  console.log('third new');
};configurable
이 속성기술자는 해당 객체로부터 그 속성을 제거할 수 있는지를 기술한다.
true 라면 삭제할 수 있다. 기본값은 false.
enumerable
해당 객체의 키가 열거 가능한지를 기술한다.
true 라면 열거가능하다. 기본값은 false.
value
속성에 해당되는 값으로 오직 적합한 자바스크립트 값 (number, object, function, etc) 만 올 수 있다. 기본값은 undefined.
writable
writable이 true로 설정되면 할당연산자 assignment operator 를 통해 값을 바꿀 수 있다. 기본값은 false.
function log(show: boolean = true) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
      show && console.log('start');
      original(...args);
      show && console.log('end');
    };
  };
}
class Test5 {
  @log()
  print1(first: string, second: number) {
    console.log('print1', first, second);
  }
  @log(false)
  print2(first: string, second: number) {
    console.log('print2', first, second);
  }
}
const test5 = new Test5();
test5.print1('mark', 36);
test5.print2('mark', 36);function propertyDecorator(target: any, propName: string) {...}
function propertyDecoratorFactory(...) {
	return function(target: any, propName: string) {...}
}target
propName
function propertyDecorator(target: any, propName: string): any {
  console.log(target);
  console.log(propName);
  return {
    writable: false
  };
}
class Test6 {
  @propertyDecorator
  name: string = 'Mark';
}
const test6 = new Test6();
test6.name = 'Anna'; // runtime errorfunction parameterDecorator(
	target: any,
	methodName: string,
	paramIndex: number
) {...}
function parameterDecoratorFactory(...) {
	return function(
		target: any,
		methodName: string,
		paramIndex: number
	) {...}
}target
methodName
paramIndex
function parameterDecorator(
	target: any,
	methodName: string,
	paramIndex: number
) {
	console.log('parameterDecorator start');
  console.log(target);
  console.log(methodName);
  console.log(paramIndex);
  console.log('parameterDecorator end');
}
class Test7 {
	private _name: string;
	private _age: number;
	constructor(name: string, @parameterDecorator age: number) {
		this._name = name;
		this._age = age;
	}
	print(@parameterDecorator message: string) {
		console.log(message);
	}
}// parameterDecorator start
// Test7 { print: [Function] }
// print
// 0
// parameterDecorator end
// parameterDecorator start
// [Function: Test7]
// undefined
// 1
// parameterDecorator endinterface StringContainer {
  value: string;
  format(): string;
  split(): string[];
}
interface NumberContainer {
  value: number;
  nearestPrime: number;
  round(): number;
}
type Item1<T> = {
  id: T,
  container: any;
};
const item1: Item1<string> = {
  id: "aaaaaa",
  container: null
};type Item2<T> = {
  id: T;
  container: T extends string ? StringContainer : NumberContainer;
};
const item2: Item2<string> = {
  id: 'aaaaaa',
  container: null, // Type 'null' is not assignable to type 'StringContainer'.
};
type Item3<T> = {
  id: T extends string | number ? T : never;
  container: T extends string
    ? StringContainer
    : T extends number
    ? NumberContainer
    : never;
};
const item3: Item3<boolean> = {
  id: true, // Type 'boolean' is not assignable to type 'never'.
  container: null, // Type 'null' is not assignable to type 'never'.
};type ArrayFilter<T> = T extends any[] ? T : never;
type StringsOrNumbers = ArrayFilter<string | number | string[] | number[]>;
// 1. string | number | string[] | number[]
// 2. never | never | string[] | number[]
// 3. string[] | number[]interface Table {
  id: string;
  chairs: string[];
}
interface Dino {
  id: number;
  legs: number;
}
interface World {
  getItem<T extends string | number>(id: T): T extends string ? Table : Dino;
}
let world: World = null as any;
const dino = world.getItem(10);
const what = world.getItem(true); // Error! Argument of type 'boolean' is not assignable to parameter of type 'string | number'.ts(2345)
type Flatten<T> = T extends any[]
  ? T[number]
  : T extends object
  ? T[keyof T]
  : T;
const numbers = [1, 2, 3];
type NumbersArrayFlattened = Flatten<typeof numbers>;
// 1. number[]
// 2. number
const person = {
  name: 'Mark',
  age: 38
};
                             
type SomeObjectFlattened = Flatten<typeof person>;
// 1. keyof T --> "id" | "name"
// 2. T["id" | "name"] --> T["id"] | T["name"] --> number | string
const isMale = true;
type SomeBooleanFlattened = Flatten<typeof isMale>;
// truetype UnpackPromise<T> = T extends Promise<infer K>[] ? K : any;
const promises = [Promise.resolve('Mark'), Promise.resolve(38)];
type Expected = UnpackPromise<typeof promises>; // string | numberfunction plus1(seed: number): number {
  return seed + 1;
}
type MyReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
type Id = MyReturnType<typeof plus1>;
lookupEntity(plus1(10));
function lookupEntity(id: Id) {
  // query DB for entity by ID
}
// type Exclude<T, U> = T extends U ? never : T;
type Excluded = Exclude<string | number, string>; // number - diff
// type Extract<T, U> = T extends U ? T : never;
type Extracted = Extract<string | number, string>; // string - filter
// Pick<T, Exclude<keyof T, K>>; (Mapped Type)
type Picked = Pick<{name: string, age: number}, 'name'>;
// type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Omited = Omit<{name: string, age: number}, 'name'>;
// type NonNullable<T> = T extends null | undefined ? never : T;
type NonNullabled = NonNullable<string | number | null | undefined>;/*
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
*/
/*
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;
*/
type MyParameters = Parameters<(name: string, age: number) => void>; // [name: string, age: number]
interface Constructor {
  new (name: string, age: number): string;
}
/*
type ConstructorParameters<
  T extends new (...args: any) => any
> = T extends new (...args: infer P) => any ? P : never;
*/
type MyConstructorParameters = ConstructorParameters<Constructor>; // [name: string, age: number]
/*
type InstanceType<T extends new (...args: any) => any> = T extends new (
  ...args: any
) => infer R
  ? R
  : any;
*/
type MyInstanceType = InstanceType<Constructor>; // string
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Person {
  id: number;
  name: string;
  hello(message: string): void;
}
type T1 = FunctionPropertyNames<Person>;
type T2 = NonFunctionPropertyNames<Person>;
type T3 = FunctionProperties<Person>;
type T4 = NonFunctionProperties<Person>;