在 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 相关文章推荐
javascript实现的在当前窗口中漂浮框的代码
Mar 15 Javascript
JQuery实现用户名无刷新验证的小例子
Mar 22 Javascript
javascript调试之DOM断点调试法使用技巧分享
Apr 15 Javascript
jQuery插件jRumble实现网页元素抖动
Jun 05 Javascript
JS遍历页面所有对象属性及实现方法
Aug 01 Javascript
vue.js表格分页示例
Oct 18 Javascript
ES6学习之变量的解构赋值
Feb 12 Javascript
js实现无缝滚动图(可控制当前滚动的方向)
Feb 22 Javascript
基于vue-cli vue-router搭建底部导航栏移动前端项目
Feb 28 Javascript
React从react-router路由上做登陆验证控制的方法
May 10 Javascript
mpvue+vuex搭建小程序详细教程(完整步骤)
Sep 30 Javascript
Vue前端项目部署IIS的实现
Jan 06 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批量上传的实现代码
2013/06/09 PHP
php对二维数组进行排序的简单实例
2013/12/19 PHP
php 判断网页是否是utf8编码的方法
2014/06/06 PHP
php中文字符串截取方法实例总结
2014/09/30 PHP
php获取网页上所有链接的方法
2015/04/03 PHP
PHP SESSION机制的理解与实例
2019/03/22 PHP
脚本收藏iframe
2006/07/21 Javascript
Eval and new funciton not the same thing
2012/12/27 Javascript
Jquery通过Ajax方式来提交Form表单的具体实现
2013/11/07 Javascript
当某个文本框成为焦点时即清除文本框内容
2014/04/28 Javascript
JQuery中节点遍历方法实例
2015/05/18 Javascript
JS获取一个未知DIV高度的方法
2016/08/09 Javascript
js中Number数字数值运算后值不对的解决方法
2017/02/28 Javascript
详解vue服务端渲染(SSR)初探
2017/06/19 Javascript
使用Fullpage插件快速开发整屏翻页的页面
2017/09/13 Javascript
详解js删除数组中的指定元素
2018/10/31 Javascript
[43:24]VG vs Serenity 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
学习python的几条建议分享
2013/02/10 Python
Python字典简介以及用法详解
2016/11/15 Python
python实现微信自动回复功能
2018/04/11 Python
详谈Numpy中数组重塑、合并与拆分方法
2018/04/17 Python
解决nohup重定向python输出到文件不成功的问题
2018/05/11 Python
如何基于python实现画不同品种的樱花树
2020/01/03 Python
运行tensorflow python程序,限制对GPU和CPU的占用操作
2020/02/06 Python
华为旗下电子商务平台:华为商城
2016/08/06 全球购物
澳大利亚领先的优质葡萄酒拍卖会:Langton’s Fine Wines
2019/03/24 全球购物
主持人婚宴答谢词
2014/01/28 职场文书
旅游业大学生创业计划书
2014/01/31 职场文书
班组长竞聘书
2014/03/31 职场文书
法制宣传月活动总结
2014/04/29 职场文书
体育教师求职信
2014/06/30 职场文书
信息与计算机科学职业规划范文:成为一艘有方向的船
2014/09/11 职场文书
小学生家长意见
2015/06/03 职场文书
2016年三八节红领巾广播稿
2015/12/17 职场文书
MySQL数据库索引的最左匹配原则
2021/11/20 MySQL
浅析Python中的随机采样和概率分布
2021/12/06 Python