После перехода на написание Vue-приложений с помощью Typescript, после раунда крещения тулчейнов и зависимостей, я смог идти медленно, но есть очень часто используемая функция mixin, и кажется, что официального решения нет.
Вы хотите не только насладиться гибкостью и удобством примесей, но также хотите воспользоваться гарантией безопасности, предоставляемой системой типов ts, и удобством использования IntelliSense во время разработки.
В официальной организации vuejs есть «vue-class-component» и рекомендованный «vue-property-decorator», которые не реализованы соответствующим образом. Просматривая предыдущий выпуск, нужно сделать фичу, которая давно висит, а именно поддержку миксина.
Это не сложно, просто напишите сами.
Сообщение: vue-class-component 6.2.0 доступенmixinsМетод аналогичен реализации идеи этой статьи.
выполнить
import Vue, { VueConstructor } from 'vue'
export type VClass<T> = {
new(): T
} & Pick<VueConstructor, keyof VueConstructor>
/**
* mixins for class style vue component
*/
function Mixins<A>(c: VClass<A>): VClass<A>
function Mixins<A, B>(c: VClass<A>, c1: VClass<B>): VClass<A&B>
function Mixins<A, B, C>(c: VClass<A>, c1: VClass<B>, c2: VClass<C>): VClass<A&B&C>
function Mixins<T>(c: VClass<T>, ...traits: Array<VClass<T>>): VClass<T> {
return c.extend({
mixins: traits
})
}
утверждениеVClass<T>
Доступен как конструктор класса для T . в то же время черезPick
Получите статические методы (extend/mixin и т. д.) в конструкторе Vue, чтобы можно было поддерживать реальную реализацию в следующем абзаце, вызвав конструктор подкласса Vue.extend
метод для создания нового конструктора подкласса.
function Mixins<T>(c: VClass<T>, ...traits: Array<VClass<T>>): VClass<T> {
return c.extend({
mixins: traits
})
}
Что касается ABC, то это чисто ручной труд по объявлениям типов.
использовать
При фактическом использовании:
import { Component, Vue } from 'vue-property-decorator'
import { Mixins } from '../../util/mixins'
@Component
class PageMixin extends Vue {
title = 'Test Page'
redirectTo(path: string) {
console.log('calling reidrectTo', path)
this.$router.push({ path })
}
}
interface IDisposable {
dispose(...args: any[]): any
}
@Component
class DisposableMixin extends Vue {
_disposables: IDisposable[]
created() {
console.log('disposable mixin created');
this._disposables = []
}
beforeDestroy() {
console.log('about to clear disposables')
this._disposables.map((d) => {
d.dispose()
})
delete this._disposables
}
registerDisposable(d: IDisposable) {
this._disposables.push(d)
}
}
@Component({
template: `
<div>
<h1>{{ title }}</h1>
<p>Counted: {{ counter }}</p>
</div>
`
})
export default class TimerPage extends Mixins(PageMixin, DisposableMixin) {
counter = 0
mounted() {
const timer = setInterval(() => {
if (this.counter++ >= 3) {
return this.redirectTo('/otherpage')
}
console.log('count to', this.counter);
}, 1000)
this.registerDisposable({
dispose() {
clearInterval(timer)
}
})
}
}
count to 1
count to 2
count to 3
calling reidrectTo /otherpage
about to clear disposables
заметьте прямоextends Vue
изDisposableMixin
не является допустимым компонентом Vue, и его нельзя использовать непосредственно вmixins
вариант, если он будет использоваться сVue.extend
способ расширить использование пользовательских компонентов, не забудьте использоватьComponent
Один слой упаковки.
const ExtendedComponent = Vue.extend({
name: 'ExtendedComponent',
mixins: [Component(DisposableMixin)],
})
Abstract class
В бизнес-системе в большинстве случаев требования будут более сложными и предусматривать некоторые базовые функции, но некоторые части нужно оставить наследникам для самостоятельной реализации, в этом случае уместно использовать абстрактные классы.
прямое наследование
abstract class AbstractMusicPlayer extends Vue {
abstract audioSrc: string
playing = false
togglePlay() {
this.playing = !this.playing
}
}
@Component
class MusicPlayerA extends AbstractMusicPlayer {
audioSrc = '/audio-a.mp3'
}
@Component
class MusicPlayerB extends AbstractMusicPlayer {
staticBase = '/statics'
get audioSrc() {
return `${this.staticBase}/audio-b.mp3`
}
}
Используйте миксины
Плохой способ: читерство и аннотации
Но абстрактные классы не могут быть созданы и не удовлетворены{ new(): T }
Следовательно, это требование может передаваться только по наследству, ине можемСмешанный, по той же причине абстрактные классы не могут быть смешаны с помощью «vue-class-component».Component
Функциональное украшение.
В это время мы должны написать реализованные функции в Mixin, поместить функции, которые должны быть реализованы в интерфейсе, и позволить реализовать их конкретным классам.
interface IMusicSourceProvider {
audioSrc: string
}
/**
* 需要实现 IPlayerImplementation
*/
@Component
class PlayerMixin extends Vue {
/** @abstract 必须实现 */
audioSrc: string
logSrc() {
console.log(this.audioSrc)
}
}
interface IPlayerImplementation extends IMusicSourceProvider {}
@Component
class OtherMixin extends Vue {
description = '另一个 Mixin'
}
@Component
class RealPlayer extends Mixins(PlayerMixin, OtherMixin) implements IPlayerImplementation {
audioSrc = '/audio-c.mp3'
}
// 无法正常工作
@Component
class BrokenPlayer extends Mixins(PlayerMixin, OtherMixin) {
}
ввиду@Component
То, как реализован декоратор, этот способ обмана компилятора на самом деле довольно неуклюж.
Если конкретный класс наследуетPlayerMixin
, но не реализован с использованием геттера или инициализатора свойстваaudioSrc
Этот атрибут, компилятор не может сообщить вам об этой ошибке (без включения строгого режима), но в реальном использованииaudioSrc
На самом деле он не инициализирован, вы найдетеBrokenPlayer
в случае_data
не содержитaudioSrc
, даже если значение устанавливается вручную после создания экземпляра, Vue не может отслеживать изменение значения, что вызовет некоторые скрытые ошибки.
Нам остается только аккуратно прописывать комментарии в коде, надеясь, что пользователи этого не забудут.
Вы также можете выполнить некоторые дополнительные проверки во время разработки, а именно:
Пользовательский декоратор AbstractProperty
vue-class-component предоставляетcreateDecoratorметод создания собственного декоратора под свою систему, мы можем использовать его так:
import { createDecorator } from 'vue-class-component'
// 一个什么都不做的装饰器,在 production 环境下启用。不使用 createDecorator
const HolderDecorator = (ctor: any) => ctor
/**
* Only for vue-class-component decorated class
*/
export const AbstractProperty = isProduction ? HolderDecorator:
createDecorator((options, key) => {
const originCreated = options.created
options.created = function () {
if (originCreated) originCreated.apply(this, arguments)
if (!(key in this)) {
console.error(`未实现 AbstractProperty '${key}'`)
}
}
})
@Component
class PlayerMixin extends Vue {
@AbstractProperty
audioSrc: string
logSrc() {
console.log(this.audioSrc)
}
}
@Component
class BrokenPlayer extends Mixins(PlayerMixin, OtherMixin) {
}
const player = new BrokenPlayer
// 未实现 AbstractProperty 'audioSrc'
Не такой уж плохой подход: промежуточные классы
@Component
class _PlayerImpl extends AbstractMusicPlayer {
audioSrc = '/audio-d.mp3'
}
@Component
export class RealPlayer2 extends Mixins(_PlayerImpl, OtherMixin) {
}
использовать промежуточные классы_PlayerImpl
Для реализации абстрактной части абстрактного класса, а затем для использования реальным пользователемRealPlayer2
использовать. Немного многословно, но безопасно.