실무에서 Typescript Mixin 적용해보기

이번에 오피스에 댓글 기능 공통 프레임워크를 개발을 하면서, 타입스크립트 Mixin을 사용해봤고, Mixin을 실무에 적용해보면서 공부했던 것들을 한번 정리해보고자 한다.

Mixin이 필요했던 상황

Mixin이란?

실무 Mixin 코드

type CommentConstructor<T extends object = object> = abstract new (...args: any[]) => T;

/**
 * CommentMixin이 결합된 클래스의 인스턴스 타입 정의
 */
export type CommentableDocumentNode = Commentable & DocumentNode;

export function CommentMixin<NodeBase extends CommentConstructor<DocumentNode>>(
  DocumentNodeBase: NodeBase
) {
  abstract class CommentableNode extends DocumentNodeBase implements Commentable {
    /**
     * CommentableDocumentNode의 commentList를 deserialize
     */
    @serializable(
      alias(
        'elementType',
        custom(
          () => SKIP,
          (value, context) => {
            const data = context.json;
            const { commentList } = data;
            if (commentList !== undefined) {
              const parsedCommentList = JSON.parse(commentList);
              context.target.setCommentList(parsedCommentList);
              return parsedCommentList;
            }
            return [];
          }
        )
      )
    )
    /**
     * CommentableDocumentNode에서 관리하는 commentList
     */
    @observable.shallow
    private commentList: Array<number> = [];

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(...args: any[]) {
      super(...args);
      makeObservable(this);
    }

    public setCommentList(commentList: Array<number>): void {
      this.commentList = commentList;
    }

    public getCommentList(): Array<number> {
      return this.commentList;
    }

    public addComment(commentId: number): void {
      this.commentList.push(commentId);
    }

    public removeComment(commentId: number): void {
      this.commentList = this.commentList.filter(curId => curId !== commentId);
    }

    public hasComment(): boolean {
      return this.commentList.length > 0;
    }

    public isCommentMixin(): boolean {
      return true;
    }
  }

  return CommentableNode;
}

/**
 * CommentableDocumentNode에 대한 타입 가드
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isCommentable = (obj: OfficeTreeNodeTypes | any): obj is CommentableDocumentNode =>
  typeof obj === 'object' &&
  obj !== null &&
  typeof obj.isCommentMixin === 'function' &&
  obj.isCommentMixin();
type CommentConstructor<T extends object = object> = abstract new (...args: any[]) => T; 
type Constructor = new (x: number, y: string) => any;
export function CommentMixin<NodeBase extends CommentConstructor<DocumentNode>>(
  DocumentNodeBase: NodeBase
) {
  abstract class CommentableNode extends DocumentNodeBase implements Commentable {
      ...
      return CommentableNode;
  }
}

Mixin 장점

끝!