[230708] Typescript 공식문서
1. Typescript Mixins 정독 및 정리 완료
믹스인이란 ?
- 클래스 구성하는 작은 클래스를 조합하여 클래스를 구축하는 방법
- 기본 클래스에 추가 기능이나 재사용 가능한 기능을 조합하여 클래스를 확장함
- 상속과 제네릭을 활용해서 구현
- 동적으로 조합 가능 → 코드 재사용성 높일 수 있고 계층 구조를 유연하게 구성 가능
믹스인 적용 방법
- 믹스인을 적용할 기본 클래스 선언
class Sprite {
name = "";
x = 0;
y = 0;
constructor(name: string) {
this.name = name;
}
}
- 클래스를 확장하는 클래스 표현식을 반환하는 유형 선언
// 전달된 타입이 클래스임을 선언하는 타입
// 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;
}
};
}
- 사용 예시
//일반 클래스를 믹스인 기법을 이용해서 재구성함 (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 컴파일러 내에 코드 플로우 분석을 통해 기본적으로 지원되지만 특정 상황에선 안될 수도 있다.
- 데코레이터 사용 불가능
// 믹스인 패턴을 복제하는 데코레이터 함수:
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'.
- 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
Leave a comment