Как создать утилиту для класса

Что я сделал
Мой первоначальный подход - использовать следующий миксин, который возвращает декоратор класса:

function createMixin (behaviour: any, sharedBehaviour: any = {}): any {
  const instanceKeys: any = Reflect.ownKeys(behaviour)
  const sharedKeys: any = Reflect.ownKeys(sharedBehaviour)
  const typeTag = Symbol(`isa`)

  function _mixin (clazz: Function): Function {
    for (let property of instanceKeys) {
      Object.defineProperty(clazz.prototype, property, {
        value: behaviour[property],
        writable: true
      })
    }

    Object.defineProperty(clazz.prototype, typeTag, { value: true })

    return clazz
  }

  for (let property of sharedKeys) {
    Object.defineProperty(_mixin, property, {
      value: sharedBehaviour[property],
      enumerable: sharedBehaviour.propertyIsEnumerable(property)
    })
  }

  Object.defineProperty(_mixin, Symbol.hasInstance, {
    value: (i: any) => !!i[typeTag]
  })

  return _mixin
}

export const customUtil = createMixin({
  customUtil (event: any) {
    console.log(this)
  }
})

Так что позже утилиту можно использовать для украшения класса и получить доступ к этому целевому классу без каких-либо проблем:

import { customUtil } from 'utils'

@customUtil
export default class ComponentClass extends Vue {
  someClassMethod() {
    this.customUtil() // successfully outputs the whole class in the console
  }
}

Но это приводит к предупреждению о tslinter TS2339: Property 'customUtil' does not exist on type 'ComponentClass'.

Вопросы
1. Есть ли возможность решить проблему линтера, каким-то образом «набрав» метод, присвоенный классу утилитой mixin
2. Есть ли альтернативный подход, чтобы иметь служебные функции / классы, у которых не будет проблем с доступом к этому классу, в котором они используются, и с простым способом их присоединения?


person volna    schedule 20.12.2019    source источник


Ответы (1)


Эта проблема обсуждалась в других темах и является также нерешенная проблема в TypeScript github. Но есть два способа обойти это.

1: Приведение типов

Вы можете просто ввести this, чтобы либо забыть о классе Context, либо дополнить его необходимой информацией.

import { customUtil } from 'utils'

@customUtil
export default class ComponentClass extends Vue {
  someClassMethod() {
    (this as any).customUtil(); // Now the linter will be fine but you will lose type safety
    (this as any as {customUtil: (event:any) => void}).customUtil(); // This works and you could/should extract the type. 
  }
}

Но, как видите, это не идеально.

2: настоящие миксы TypeScript

Вы можете использовать настоящие миксины TypeScript вместо декораторов:

Утилиты

type Constructor<T = {}> = new (...args: any[]) => T;

// The Mixin
export function WithUtils<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    customUtil (event?: any) { // Actually, 'event' needs to be optional
      console.log(this)
    }
  };
}

// A Base class so you can just extend if needed
export const UtilsBase = WithUtils(class {});

Компонент

export default class ComponentClass extends WithUtils(Vue) {
  someClassMethod() {
    this.customUtil() // successfully outputs the whole class in the console
  }
}

Класс не является наследником Vue

export default class SomeClass extends UtilsBase {
  someClassMethod() {
    this.customUtil() // successfully outputs the whole class in the console
  }
}

// alternatively, you can use the Mixin with an unnamed class
export default class SomeClass extends WithUtils(class {}) {
  someClassMethod() {
    this.customUtil() // successfully outputs the whole class in the console
  }
}
person pascalpuetz    schedule 20.12.2019
comment
Паскаль большое спасибо за помощь, я пошел по пути исследования, но в конце концов я вернулся к идее, которую вы предлагаете здесь. Желаю вам всего наилучшего :-) - person volna; 22.12.2019
comment
Вы только почувствовали небольшую проблему с Cannot find name 'T'. для type Constructor<T = {}> = new (...args: any[]) => T, не могли бы вы предложить возможное решение? :-) - person volna; 22.12.2019
comment
Я пытался объявить declare module 'T' { const T: any export = T }, но теперь удачи :) - person volna; 22.12.2019
comment
@volna Рад, что помог! :) Для вашей проблемы: T не модуль, а общий. Он должен компилироваться как есть. Какую версию TypeScript вы используете? - person pascalpuetz; 23.12.2019
comment
Прямо сейчас в проекте используется 3.5.3 :-) - person volna; 23.12.2019
comment
3.5.3 определенно может обрабатывать дженерики - TypeScript Playground вернулся к 2.4.1, и даже он может справиться с этим ... Странно. Вы как-то изменили эту строчку? Что он говорит, когда вы копируете и вставляете его в TypeScript Playground? У меня немного нет идей, так как код, который я опубликовал, работает на детской площадке ... - person pascalpuetz; 23.12.2019
comment
Я дважды проверил код, и вы абсолютно правы, я действительно изменил строку: shy: еще раз спасибо, Паскаль :) - person volna; 23.12.2019