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


Posted in Javascript onApril 17, 2018

转到用 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功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
建立良好体验度的Web注册系统ajax
Jul 09 Javascript
ASP小贴士/ASP Tips javascript tips可以当桌面
Dec 10 Javascript
jQuery事件 delegate()使用方法介绍
Oct 30 Javascript
javascript实现可全选、反选及删除表格的方法
May 15 Javascript
js获取Html元素的实际宽度高度的方法
May 19 Javascript
浅谈js函数的多种定义方法与区别
Nov 29 Javascript
layui 根据后台数据动态创建下拉框并同时默认选中的实例
Sep 02 Javascript
原生JS实现无缝轮播图片
Jun 24 Javascript
解决vue自定义指令导致的内存泄漏问题
Aug 04 Javascript
Vuex实现购物车小功能
Aug 17 Javascript
JavaScript中Object、map、weakmap的区别分析
Dec 15 Javascript
Element-ui 自带的两种远程搜索(模糊查询)用法讲解
Jan 29 Javascript
vue iview实现动态路由和权限验证功能
Apr 17 #Javascript
基于VuePress 轻量级静态网站生成器的实现方法
Apr 17 #Javascript
Vue-cropper 图片裁剪的基本原理及思路讲解
Apr 17 #Javascript
js闭包学习心得总结
Apr 17 #Javascript
Vue使用json-server进行后端数据模拟功能
Apr 17 #Javascript
js实现点击按钮复制文本功能
Jul 20 #Javascript
Element-UI Table组件上添加列拖拽效果实现方法
Apr 14 #Javascript
You might like
又一个php 分页类实现代码
2009/12/03 PHP
PHP使用DOMDocument类生成HTML实例(包含常见标签元素)
2014/06/25 PHP
浅析iis7.5安装配置php环境
2015/05/10 PHP
php支付宝在线支付接口开发教程
2016/09/19 PHP
简介JavaScript中的getSeconds()方法的使用
2015/06/10 Javascript
javascript实现根据3原色制作颜色选择器的方法
2015/07/17 Javascript
js随机生成字母数字组合的字符串 随机动画数字
2015/09/02 Javascript
基于JavaScript实现瀑布流效果(循环渐近)
2016/01/27 Javascript
jquery ajax结合thinkphp的getjson实现跨域的方法
2016/06/06 Javascript
javascript实现去除HTML标签的方法
2016/12/26 Javascript
Bootstrap table表格简单操作
2017/02/07 Javascript
JavaScript实现翻页功能(附效果图)
2017/02/16 Javascript
BootStrap中jQuery插件Carousel实现轮播广告效果
2017/03/27 jQuery
基于jQuery实现图片推拉门动画效果的两种方法
2017/08/26 jQuery
详解js 创建对象的几种方法
2019/03/08 Javascript
[07:37]DOTA2-DPC中国联赛2月2日Recap集锦
2021/03/11 DOTA
Python学习_几种存取xls/xlsx文件的方法总结
2018/05/03 Python
python基于property()函数定义属性
2020/01/22 Python
Django实现前台上传并显示图片功能
2020/05/29 Python
Python爬虫防封ip的一些技巧
2020/08/06 Python
Python如何定义有默认参数的函数
2020/08/10 Python
基于OpenCV的路面质量检测的实现
2020/11/04 Python
梅西酒窖:Macy’s Wine Cellar
2018/01/07 全球购物
英国领先的运动物理治疗供应公司:Vivomed
2018/07/14 全球购物
整个世界的设计师家具在哈恩:Designathome
2019/03/25 全球购物
药剂专业学生求职信范文
2013/12/28 职场文书
体育教师个人的自我评价
2014/02/16 职场文书
小学生综合素质评语
2014/04/23 职场文书
志愿者宣传口号
2014/06/17 职场文书
驾驶员安全责任书范本
2014/07/24 职场文书
村创先争优活动总结
2014/08/28 职场文书
个人学习群众路线心得体会
2014/11/05 职场文书
2015年公务员个人工作总结
2015/04/24 职场文书
煤矿安全学习心得体会
2016/01/18 职场文书
python简单验证码识别的实现过程
2021/06/20 Python
python中sqllite插入numpy数组到数据库的实现方法
2021/06/21 Python