在 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语言的思想前提想学习js的朋友可以看看
Apr 01 Javascript
HTML中的setCapture和releaseCapture使用介绍
Mar 21 Javascript
Ajax请求在数据量大的时候出现超时的解决方法
Feb 27 Javascript
JS实现的一个简单的Autocomplete自动完成例子
Apr 16 Javascript
node.js中的fs.rmdir方法使用说明
Dec 16 Javascript
为JS扩展Array.prototype.indexOf引发的问题及解决办法
Jan 21 Javascript
js实现的简练高效拖拽功能示例
Dec 21 Javascript
bootstrap组件之按钮式下拉菜单小结
Jan 19 Javascript
浅谈vue项目优化之页面的按需加载(vue+webpack)
Dec 11 Javascript
仿vue-cli搭建属于自己的脚手架的方法步骤
Apr 17 Javascript
Vue+Koa2+mongoose写一个像素绘板的实现方法
Sep 10 Javascript
JS数组方法some、every和find的使用详情
Oct 05 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
美图秀秀web开放平台--PHP流式上传和表单上传示例分享
2014/06/22 PHP
遍历指定目录,并存储目录内所有文件属性信息的php代码
2016/10/28 PHP
ext读取两种结构的xml的代码
2008/11/05 Javascript
javascript 鼠标滚轮事件
2009/04/09 Javascript
js substr支持中文截取函数代码(中文是双字节)
2013/04/17 Javascript
创建你的第一个AngularJS应用的方法
2015/06/16 Javascript
jQuery实现伪分页的方法分享
2016/02/17 Javascript
vue实现a标签点击高亮方法
2018/03/17 Javascript
vue使用v-for实现hover点击效果
2018/09/29 Javascript
Vue服务端渲染实践之Web应用首屏耗时最优化方案
2019/03/22 Javascript
JavaScript前端页面搜索功能案例【基于jQuery】
2019/07/10 jQuery
深入理解Antd-Select组件的用法
2020/02/25 Javascript
使用vue实现HTML页面生成图片的方法
2020/03/12 Javascript
JavaScript 如何计算文本的行数的实现
2020/09/14 Javascript
对于Python异常处理慎用“except:pass”建议
2015/04/02 Python
在Python的Flask框架中使用模版的入门教程
2015/04/20 Python
Python ftp上传文件
2016/02/13 Python
【python】matplotlib动态显示详解
2019/04/11 Python
Python 爬虫实现增加播客访问量的方法实现
2019/10/31 Python
Python opencv相机标定实现原理及步骤详解
2020/04/09 Python
Spy++的使用方法及下载教程
2021/01/29 Python
HTML5是什么 HTML5是什么意思 HTML5简介
2012/10/26 HTML / CSS
意大利奢侈品网站:Italist
2016/08/23 全球购物
如何估计一张表的大小(假设该表中有1万条数据)
2016/03/27 面试题
中专自我鉴定范文
2013/10/16 职场文书
生产车间班组长岗位职责
2014/01/06 职场文书
茶叶店创业计划书范文
2014/01/19 职场文书
小学生竞选班长演讲稿
2014/04/24 职场文书
地质工程专业毕业生求职信
2014/08/08 职场文书
2014业务员年终工作总结
2014/12/09 职场文书
婚礼答谢词
2015/01/04 职场文书
升学宴学生答谢词
2015/01/05 职场文书
西柏坡导游词
2015/02/05 职场文书
孙振耀退休感言
2015/08/01 职场文书
JavaGUI模仿QQ聊天功能完整版
2021/07/04 Java/Android
javascript之Object.assign()的痛点分析
2022/03/03 Javascript