在 Typescript 中使用可被复用的 Vue Mixin功能

转到用 Typescript 写 Vue 应用以后,经过一轮工具链和依赖的洗礼,总算蹒跚地能走起来了,不过有一个很常用的功能 mixin,似乎还没有官方的解决方案。

既想享受 mixin 的灵活和方便,又想收获 ts 的类型系统带来的安全保障和开发时使用 IntelliSense 的顺滑体验。

vuejs 官方组织里有一个 'vue-class-component' 以及连带推荐的 'vue-property-decorator',都没有相应实现。翻了下前者的 issue,有一条挂了好些时间的待做 feature 就是 mixin 的支持。

也不是什么复杂的事,自己写一个吧。

后注: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 拿到 Vue 的构造器上的静态方法(extend/mixin 之类),如此才能够支持下面这段中的真正实现,通过调用一个 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
}
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

在业务系统中会使用到的 Mixin 其实多数情况下会更复杂,提供一些基础功能,但有些部分需要留给继承者自行实现,这个时候使用抽象类就很合适。

abstract class AbstractMusicPlayer extends Vue {
 abstract audioSrc: string
 playing = false
 togglePlay() {
  this.playing = !this.playing
 }
}
class MusicPlayerA extends AbstractMusicPlayer {
 audioSrc = '/audio-a.mp3'
}
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
}
/**
 * @implements IPlayerImplementation
 */
class PlayerMixin extends Vue {
 /** @abstract */
 audioSrc: string
 logSrc() {
  console.log(this.audioSrc)
 }
}
interface IPlayerImplementation extends IMusicSourceProvider {}
class RealPlayer extends Mixins(PlayerMixin) implements IPlayerImplementation {
 audioSrc = '/audio-c.mp3'
}

这种欺骗编译器的方式其实还是比较拙劣的,如果一个具体类继承了 PlayerMixin,却没有显示声明实现 IPlayerImplementation ,编译器无法告诉你这个错误。我们只能在代码里小心翼翼写上注释,期待使用者不要忘了这件事。

总结

以上所述是小编给大家介绍的在 Typescript 中使用可被复用的 Vue Mixin功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 谈谈vue中mixin的一点理解

    vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用.最开始我一度认为这个和组件好像没啥区别..后来发现错了.下面我们来看看mixins和普通情况下引入组件有什么区别? 组件在引用之后相当于在父组件内开辟了一块单独的空间,来根据父组件props过来的值进行相应的操作,单本质上两者还是泾渭分明,相对独立. 而mixins则是在引入组件之后,则是将组件内部的内容如data等方法.method等属性与父组件相应内容进行合并.相当于在引入后,父组件的各种属性方法都被扩充了.    

  • 在Vue.js中使用Mixins的方法

    一个很常见的场景: 有两个非常相似的组件, 它们拥有非常相似的基本功能, 但是它们之间又有足够的不同的地方, 该如何选择呢? 我们是应该将它们分成两个完全不同的组件呢? 还是创建一个基础组件, 然后定义足够多的props以方便区分使用场景? 这两种方式都不是完美的: 如果你将它们分成两个完全不同的组件, 在需求变化(功能变化)时, 可能会增加需要同时修改两个组件的风险, 这违反了"DRY"的前提. 另一方面, 太多的props很快会让人变得凌乱, 并且, 迫使维护人员, 甚至是你自己,

  • 详解Vue.js Mixins 混入使用

    Mixins一般有两种用途: 1.在你已经写好了构造器后,需要增加方法或者临时的活动时使用的方法,这时用混入会减少源代码的污染. 2.很多地方都会用到的公用方法,用混入的方法可以减少代码量,实现代码重用. 一.Mixins的基本用法 我们现在有个数字点击递增的程序,假设已经完成了,这时我们希望每次数据变化时都能够在控制台打印出提示:"数据发生变化". 代码实现过程: <!DOCTYPE html> <html lang="en"> <h

  • vue mixins组件复用的几种方式(小结)

    最近在做项目的时候,研究了mixins,此功能有妙处.用的时候有这样一个场景,页面的风格不同,但是执行的方法,和需要的数据非常的相似.我们是否要写两种组件呢?还是保留一个并且然后另个一并兼容另一个呢? 不管以上那种方式都不是很合理,因为组件写成2个,不仅麻烦而且维护麻烦:第二种虽然做了兼容但是页面逻辑造成混乱,必然不清晰:有没有好的方法,有那就是用vue的混合插件mixins.混合在Vue是为了提出相似的数据和功能,使代码易懂,简单.清晰. 1.场景 假设我们有几个不同的组件,它们的工作是切换状

  • 详解Vue 中 extend 、component 、mixins 、extends 的区别

    new Vue().component 首先我们来约定一个选项对象 baseOptions,后面的代码会用到. let options = { template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } }, created(){ conso

  • Vue使用mixins实现压缩图片代码

    本文介绍了Vue使用mixins实现压缩图片代码,分享给大家,具体如下: 图片压缩 创建mixins image-compress.js export default { methods: { /** * 检查并压缩图片大小 */ checkAndHandleCompression(file) { return new Promise((resolve, reject) => { this.imgBase64(file, (image, canvas) => { let maxSize = 2

  • 详解Vue.js组件可复用性的混合(mixin)方式和自定义指令

    混合是什么 混合 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式.混合对象可以包含任意组件选项.以组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项. 例如: var tpl1={ template:'#stpl1', data:function(){ return {msg:false} }, methods:{ msgf:function(){ this.msg=!this.msg } } } var tpl2={ template:'#stpl2', d

  • 详解vue mixins和extends的巧妙用法

    vue提供了mixins.extends配置项,最近使用中发现很好用. 混合mixins和继承extends 看看官方文档怎么写的,其实两个都可以理解为继承,mixins接收对象数组(可理解为多继承),extends接收的是对象或函数(可理解为单继承). 继承钩子函数 const extend = { created () { console.log('extends created') } } const mixin1 = { created () { console.log('mixin1

  • 在 Typescript 中使用可被复用的 Vue Mixin功能

    转到用 Typescript 写 Vue 应用以后,经过一轮工具链和依赖的洗礼,总算蹒跚地能走起来了,不过有一个很常用的功能 mixin,似乎还没有官方的解决方案. 既想享受 mixin 的灵活和方便,又想收获 ts 的类型系统带来的安全保障和开发时使用 IntelliSense 的顺滑体验. vuejs 官方组织里有一个 'vue-class-component' 以及连带推荐的 'vue-property-decorator',都没有相应实现.翻了下前者的 issue,有一条挂了好些时间的待

  • 深入理解JavaScript和TypeScript中的class

    前言 对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影.所以 设计模式 也是大多数前端开发者的一个短板. 最近在学习 Angular的过程中发现其大量的运用了 class,不得不佩服,Angular 确实是一个优秀的.值得深入研究的 框架. 本文将简单的介绍一下 JavaScript 和 TypeScript 中的 class. 基本概念 在介绍 class 之前,要先

  • TypeScript中type和interface的区别及注意事项

    目录 前言 概念 type interface 异同点 不同点 相同点 补充:Ts中type和interface定义类型扩展类型的方法 总结 前言 在 TS 中,type 和 interface相似,都可以给类型命名并通过该名字来引用表示的类型.不过它们之间是存在一些差别的,我们在使用时也需要注意一些特殊场景. 概念 type type关键字是声明类型别名的关键字.它的语法如下: type AliasName = Type; type:声明类型别名的关键字 AliasName:类型别名的名称 T

  • TypeScript 中接口详解

    在 TypeScript 中,接口是用作约束作用的,在编译成 JavaScript 的时候,所有的接口都会被擦除掉,因为 JavaScript 中并没有接口这一概念. 先看看一个简单的例子: function printLabel(labelledObj: { label: string }) { console.log(labelledObj.label); } var myObj = { size: 10, label: "Size 10 Object" }; printLabel

  • 简单了解TypeScript中如何继承 Error 类

    前言 在JavaScript 中很多时候都需要自定义错误,尤其是开发 Node.js 应用的时候. 比如一个典型的网站服务器可能需要有 NetworkError, DatabaseError, UnauthorizedError 等. 我们希望这些类都拥有 Error 的特性:有错误消息.有调用栈.有方便打印的 toString 等. 最直观的实现方式便是 继承 Error 类. 但考虑 TypeScript 需要编译到 ES5 兼容性问题会较为复杂, 本文用来帮助理解 TypeScript 中

  • Vue+Typescript中在Vue上挂载axios使用时报错问题

    在vue项目开发过程中,为了方便在各个组件中调用axios,我们通常会在入口文件将axios挂载到vue原型身上,如下: main.ts import Vue from 'vue' import axios from './utils/http' Vue.prototype.$axios = axios; 这样的话,我们在各个组件中进行请求时,就可以直接使用this.$axios,但是在ts中使用this.$axios进行请求时,会进行报错,如下所示: 从图中我们可以看出ts在Vue身上检测不到

  • TypeScript中使用getElementXXX()的示例代码

    简述 Angular 1.x版本是用JavaScript编写的,我们在百度Angular经常会搜索到AngularJS,并不是JavaScript的什么衍生版本,就是Angular 1.x.在后续版本中,改用TypeScript来重写了Angular框架.改动较大,所以做了个区分,Angular v1.x就叫AngularJS,v2及后续版本统称为Angular. 查资料和解决方案的时候,经常会搜索到大量的AngularJS内容,注意区分. 在这里提一下Angular的历史,是因为本文是在使用这

  • JavaScript和TypeScript中的void的具体使用

    如果你来自传统的强类型语言,可能会很熟悉 void 的概念:一种类型,告诉你函数和方法在调用时不返回任何内容. void 作为运算符存在于 JavaScript 中,而作为基本类型存在于 TypeScript 中.在这两个世界中,void 的工作机制与大多数人习惯的有点不同. JavaScript 中的 void JavaScript 中的 void 是一个运算符,用于计算它旁边的表达式.无论评估哪个表达式,void总是返回undefined. let i = void 2; // i ===

  • TypeScript中的方法重载详解

    前言 方法重载(overload)在传统的静态类型语言中是很常见的.JavaScript 作为动态语言, 是没有重载这一说的.一是它的参数没有类型的区分,二是对参数个数也没有检查.虽然语言层面无法自动进行重载,但借助其动态的特性,我们可以在代码中手动检查入参的类型,或者通过 arguments 获取到参数个数,从而实现根据不同的入参做不同的操作. 比如有一个获取聊天消息的方法,根据传入的参数从数组中查找数据.如果入参为数字,则认为是 id,然后从数据源中找对应 id 的数据并返回,否则当成类型,

  • RxJS在TypeScript中的简单使用详解

    1. 安装 # 安装 typescript, rxjs 包 npm install -D typescript @types/node npm install rxjs 2. 使用 2.1 使用 from 来从数组生成源 RxJS 有许多创建源的方法,如 from, fromEvent..., 这里使用 from做个例子 import {from} from 'rxjs' // 从数组生成可订阅对象 // obser 的对象类型为 Observable let obser = from([1,2

随机推荐