Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript 5 달라진 것들 #4

Open
doong-jo opened this issue Jul 29, 2023 · 1 comment
Open

TypeScript 5 달라진 것들 #4

doong-jo opened this issue Jul 29, 2023 · 1 comment
Assignees

Comments

@doong-jo
Copy link
Collaborator

doong-jo commented Jul 29, 2023

TypeScript 5

Decorators

Untitled

기존의 알맹이(Object)에 껍데기(Decorator)를 씌운다.

Before

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}
const p = new Person("Ray");
p.greet();
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    greet() {
	// 로깅 로직 추가
        console.log("LOG: Entering method.");
        console.log(`Hello, my name is ${this.name}.`);
        console.log("LOG: Exiting method.")
    }
}
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    greet() {
	// 로깅 로직 추가
        console.log("LOG: Entering method.");
        console.log(`Hello, my name is ${this.name}.`);
        console.log("LOG: Exiting method.")
    }

    hello() {
	// 로깅 로직 추가
        console.log("LOG: Entering method.");
        console.log(`Hello, my name is ${this.name}.`);
        console.log("LOG: Exiting method.")
    }
}

Use Decorator

function loggedMethod(originalMethod: any, _context: any) {
    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }
    return replacementMethod;
}
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
		// great이 loggedMethod의 인수로 전달된다.
    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
    
    @loggedMethod
    hello() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}
const p = new Person("Ray");
p.greet();
// Output:
//
//   LOG: Entering method.
//   Hello, my name is Ray.
//   LOG: Exiting method.

주의할 점

  1. class 내부 에서만 사용할 수 있다.
  2. runtime에 호출 된다.

실제 활용 사례

에러 클래스 생성 및 로깅 로직은 decorator로 재사용 가능하도록 활용

import { captureException } from 'sentry/nextjs';

function logSentry(targetClass: any) {
  // the new constructor behaviour
  const wrapper : any = function (...args) {
    // setnry에 로깅
    sentry.captureException(error, { level: 'error', extra: { args } });

    return new original(...args);
  }

  wrapper.prototype = targetClass.prototype;

  return wrapper;
}

@logSentry
export class ImageError extends Error {
  constructor(imageUrl: string, pathname: string, level?: Sentry.SeverityLevel) {
    super(`${imageUrl}을 로드하는 중 에러가 발생했습니다 (page url: ${pathname})`);

    this.name = 'ImageError';
  }
}

@logSentry
export class NetworkError extends Error {
  public statusCode: number;
  constructor(statusCode: number, responseData?: string | Record<string, any>) {
    const message =
      typeof responseData === 'string'
        ? `${statusCode}: ${responseData}`
        : `${statusCode}: ${JSON.stringify(responseData)}`;

    super(message);

    this.name = 'NetworkError';
  }
}

const Type Parameters

TS Playground

type HasNames = { names: readonly string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}

// 우리가 원하는 타입:
//    readonly ["Alice", "Bob", "Eve"]
// 하지만 실제 추론된 타입:
//    string[]
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});
// 하지만 우리는 정확하게 아래처럼 되길 원합니다. 하지만 `as const`를 항상 붙이는 것은 잊기 쉽습니다.
// readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

//////////////// TypeScript 5, const Type Parameters 이후 //////////////////////////

type TS5_HasNames = { names: readonly string[] };
function ts5_getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
//                           ^^^^^
    return arg.names;
}
// 추론된 타입: readonly ["Alice", "Bob", "Eve"]
// TypeScript 4.
'as const'  불필요
const names = ts5_getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
@ed-jinyoung-park
Copy link
Collaborator

ed-jinyoung-park commented Jul 29, 2023

All enums Are Union enums

  • Typescript가 초기의 enum은 같은 타입의 숫자 집합
enum E {
    Foo = 10,
    Bar = 20,
}
  • E.FooE.Bar는 type E로 예상되는 모든 것에 할당 가능하다는 것.
  • value에 해당하는 값을 직접 할당하면 에러
function takeValue(e: E) {}
takeValue(E.Foo); // works
takeValue(123); // error!
  • TS 2.0에서 enum literal type이 도입. enum 자체를 union으로 사용하여 subset을 만들 수도 있음.
// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet
enum Color {
    Red, Orange, Yellow, Green, Blue, /* Indigo, */ Violet
}
// Each enum member has its own type that we can refer to!
type PrimaryColor = Color.Red | Color.Green | Color.Blue;
function isPrimaryColor(c: Color): c is PrimaryColor {
    // Narrowing literal types can catch bugs.
    // TypeScript will error here because
    // we'll end up comparing 'Color.Red' to 'Color.Green'.
    // We meant to use ||, but accidentally wrote &&.
    return c === Color.Red && c === Color.Green && c === Color.Blue;
}
  • TS 5.0에서는 계산된 각 멤버에 고유한 타입을 생성하여 모든 enum을 union enum으로 만들 수 있음.
enum E {
    Blah = Math.random()
}

const a: E.Blah = 10

enum vs union type

// enum
enum Fruit {
	APPLE = 'APPLE',
    BANANA = 'BANANA',
    KIWI = 'KIWI'
}

// union type
type Fruit = 'apple' | 'banana' | 'kiwi'
  • enum의 경우 js object로 컴파일되기 때문에 작은 차이지만 번들 사이즈에 영향을 줄 수 있음.
image image

ts playground

Speed, Memory, and Package Size Optimizations

image

build 속도 향상

  • compiler의 속도가 10~25% 증가
  • namespaces 방식에서 modules 방식으로 전환 -> scope lifting의 효과를 가져옴
  • namespace 방식은 IIFE의 형태로 컴파일되어 하나의 object에 property들로 추가됨.
  • 아래와 같이 object->property로 접근해야 해서 overhead가 생김. (Non-local accesses to exports of that namespace are implicitly fully qualified, incurring the overhead of an object access)
// Namespace definition
namespace MyNamespace {
  export const myVariable = 10;
}

// Accessing the exported member
const value = MyNamespace.myVariable;
  • modules 방식은 top scope에 모든 것들을 선언하여 직접 호출이 가능

package 용량 감소

  • 63.5MB에서 35.6MB로 약 40% 감소
  • 중복되는 파일들 제거
  • bundle file의 indent 축소 (4space -> 2space)
  • tsc.jstypingsInstaller.js에서의 tree shaking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants