在 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 相关文章推荐
jquery api参考 visualjquery 中国线路 速度快
Nov 30 Javascript
jquery入门—访问DOM对象方法
Jan 07 Javascript
javascript中类的定义方式详解(四种方式)
Dec 22 Javascript
JavaScript模拟鼠标右键菜单效果
Dec 08 Javascript
jQuery ajax MD5实现用户注册即时验证功能
Oct 11 Javascript
Bootstrap源码解读导航(6)
Dec 23 Javascript
jQuery+CSS3实现点赞功能
Mar 13 Javascript
react-native DatePicker日期选择组件的实现代码
Sep 12 Javascript
js实现QQ面板拖拽效果(慕课网DOM事件探秘)(全)
Sep 19 Javascript
jQuery使用bind函数实现绑定多个事件的方法
Oct 11 jQuery
微信小程序实现全国机场索引列表
Jan 31 Javascript
js 闭包深入理解与实例分析
Mar 19 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
Codeigniter中mkdir创建目录遇到权限问题和解决方法
2014/07/25 PHP
PHP内置的Math函数效率测试
2014/12/01 PHP
PHP判断用户是否已经登录(跳转到不同页面或者执行不同动作)
2016/09/22 PHP
PHP实现微信JS-SDK接口选择相册及拍照并上传的方法
2016/12/05 PHP
用js遍历 table的脚本
2008/07/23 Javascript
超轻量级的基于jquery的三级展开列表
2011/04/26 Javascript
JS高级笔记
2011/07/13 Javascript
jquery放大镜效果超漂亮噢
2013/11/15 Javascript
Extjs grid添加一个图片状态或者按钮的方法
2014/04/03 Javascript
fckeditor粘贴Word时弹出窗口取消的方法
2014/10/30 Javascript
js实现点击添加一个input节点
2014/12/05 Javascript
JavaScript显示当前文档最后修改日期的方法
2015/03/19 Javascript
jQuery实现微信长按识别二维码功能
2016/08/26 Javascript
jQuery simpleModal插件的使用介绍
2016/08/30 Javascript
js实现3D图片展示效果
2017/03/09 Javascript
js实现全选反选不选功能代码详解
2019/04/24 Javascript
微信小程序云开发如何实现数据库自动备份实现
2019/08/16 Javascript
[01:01:43]EG vs VP 2018国际邀请赛淘汰赛BO3 第二场 8.24
2018/08/25 DOTA
发布你的Python模块详解
2016/09/15 Python
python 队列详解及实例代码
2016/10/18 Python
Python学习小技巧总结
2018/06/10 Python
python中实现字符串翻转的方法
2018/07/11 Python
django框架基于模板 生成 excel(xls) 文件操作示例
2019/06/19 Python
使用Pyhton集合set()实现成果查漏的例子
2019/11/24 Python
PyQt5事件处理之定时在控件上显示信息的代码
2020/03/25 Python
python的setattr函数实例用法
2020/12/16 Python
python drf各类组件的用法和作用
2021/01/12 Python
Python3.9.0 a1安装pygame出错解决全过程(小结)
2021/02/02 Python
Original Penguin美国官网:布拉德皮特、强尼德普喜爱的服装品牌
2016/10/25 全球购物
应届生新闻编辑求职信
2013/11/19 职场文书
我的大学生活职业生涯规划
2014/01/02 职场文书
烹调加工管理制度
2014/02/04 职场文书
经济担保书范文
2014/04/02 职场文书
退税申请报告怎么写
2015/05/18 职场文书
离婚起诉书怎么写
2015/05/19 职场文书
2020年元旦祝福语录,总有适合你的
2019/12/31 职场文书