[230708] Typescript 공식문서

1. Typescript Mixins 정독 및 정리 완료

믹스인이란 ?

  • 클래스 구성하는 작은 클래스를 조합하여 클래스를 구축하는 방법
  • 기본 클래스에 추가 기능이나 재사용 가능한 기능을 조합하여 클래스를 확장함
  • 상속과 제네릭을 활용해서 구현
  • 동적으로 조합 가능 → 코드 재사용성 높일 수 있고 계층 구조를 유연하게 구성 가능

믹스인 적용 방법

  1. 믹스인을 적용할 기본 클래스 선언
class Sprite {
  name = "";
  x = 0;
  y = 0;

  constructor(name: string) {
    this.name = name;
  }
}
  1. 클래스를 확장하는 클래스 표현식을 반환하는 유형 선언
// 전달된 타입이 클래스임을 선언하는 타입
// Constructor는 클래스 생성자를 나타내는 타입임
// ...args -> 가변인수, 생성자에 전달될 수 있는 인수 타입은 모든타입
// {} -> 생성자로 생성되는 객체 타입을 나타냄
type Constructor = new (...args: any[]) => {};

//일반 클래스를 기능 추가해주는 함수
function Scale<TBase extends Constructor>(Base: TBase) {
  return class Scaling extends Base {
    _scale = 1;

    setScale(scale: number) {
      this._scale = scale;
    }

    get scale(): number {
      return this._scale;
    }
  };
}
  1. 사용 예시
//일반 클래스를 믹스인 기법을 이용해서 재구성함 (Scale 기능 추가)
const EightBitSprite = Scale(Sprite);

//재구성한 클래스를 만듬
const flappySprite = new EightBitSprite("Bird");
//"Bird"는 일반클래스에서 선언된 name 으로 들어감

//기능 추가 클래스 함수 사용 가능
flappySprite.setScale(0.8);

//기능 추가 클래스 변수 사용 가능
console.log(flappySprite.scale);

제약이 있는 믹스인

  • 믹스인 기본 클래스에 대한 내부 지식이 없어서 원하는 디자인을 만들기 어려울땐 제네릭
// 생성자로 생성된 객체의 타입을 제한할 수 있는 방법이 없다.
type Constructor = new (...args: any[]) => {};

// 제너릭 버전은 T를 통해 생성자로 생성되는 객체 타입을 지정할 수 있음
type GConstructor<T = {}> = new (...args: any[]) => T;
  • 특정한 기본 클래스에서만 작동하는 믹스인을 생성할 수도 있다.
type GConstructor<T = {}> = new (...args: any[]) => T;

class Sprite {
  name = "";
  x = 0;
  y = 0;

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

  //얘가 없었다면 Positionable - Jumpable 코드는 오류남
  setPos(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;

function Jumpable<TBase extends Positionable>(Base: TBase) {
  return class Jumpable extends Base {
    jump() {
      // This mixin will only work if it is passed a base
      // class which has setPos defined because of the
      // Positionable constraint.
      this.setPos(0, 20);
    }
  };
}

function Drawable<TBase extends Spritable>(Base: TBase) {
  return class Drawable extends Base {
    draw() {
      console.log("그리기");
    }
  };
}

// Create a class with mixins
const JumpingSprite = Jumpable(Sprite);
const DrawingSprite = Drawable(Sprite);

// Create an instance of the class
const sprite = new JumpingSprite("MySprite");
const sprite2 = new DrawingSprite("DrawSprite");

sprite.jump();
sprite2.draw();
sprite.setPos(10, 20);
console.log(sprite.name); // Output: MySprite
console.log(sprite.x); // Output: 10
console.log(sprite.y); // Output: 20

대체 패턴

이 문서의 이전 버전에서 믹스인을 작성하는 방법으로 런타임 및 타입 계층을 별도로 생성한 다음 마지막에 병합하는 방법을 권장했다고 한다.

// 각 믹스인은 전통적인 ES 클래스입니다.
class Jumpable {
  jump() {}
}

class Duckable {
  duck() {}
}

// 기본 클래스를 포함합니다.
class Sprite {
  x = 0;
  y = 0;
}

// 기대하는 믹스인을 기본 클래스와 동일한 이름으로
// 병합하는 인터페이스를 생성합니다.
interface Sprite extends Jumpable, Duckable {}
// 런타임에 믹스인을 기본 클래스에 적용합니다.
applyMixins(Sprite, [Jumpable, Duckable]);

let player = new Sprite();
player.jump();
console.log(player.x, player.y);

// 이는 코드베이스 어디에서나 사용할 수 있습니다.
//이게 믹스해주는 코드인듯 , 잘은 모르겠다. .
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}

제약사항

  • 믹스인 패턴은 TS 컴파일러 내에 코드 플로우 분석을 통해 기본적으로 지원되지만 특정 상황에선 안될 수도 있다.
  1. 데코레이터 사용 불가능
// 믹스인 패턴을 복제하는 데코레이터 함수:
const Pausable = (target: typeof Player) => {
return class Pausable extends target {
shouldFreeze = false;
};
};

@Pausable
class Player {
x = 0;
y = 0;
}

// Player 클래스에는 데코레이터의 타입이 병합되지 않습니다:
const player = new Player();
player.shouldFreeze; **==> 오류남**
Property 'shouldFreeze' does not exist on type 'Player'.
Property 'shouldFreeze' does not exist on type 'Player'.

  1. Static 속성 믹스인
  • 싱글턴을 생성함
function base<T>() {
  class Base {
    static prop: T;
  }
  return Base;
}

function derived<T>() {
  class Derived extends base<T>() {
    static anotherProp: T;
  }
  return Derived;
}

class Spec extends derived<string>() {}

Spec.prop; // string
Spec.anotherProp; // string

Categories:

Updated:

Leave a comment