在 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 相关文章推荐
ExtJS 2.2.1的grid控件在ie6中的显示问题
May 04 Javascript
Prototype Number对象 学习
Jul 19 Javascript
jQuery的运行机制和设计理念分析
Apr 05 Javascript
一款由jquery实现的整屏切换特效
Sep 15 Javascript
JavaScript中的Web worker多线程API研究
Dec 06 Javascript
Web开发必知Javascript技巧大全
Feb 23 Javascript
JS模拟实现ECMAScript5新增的数组方法
Mar 20 Javascript
webpack4 从零学习常用配置(小结)
May 28 Javascript
小程序实现层叠卡片滑动效果
Aug 26 Javascript
ZK中使用JS读取客户端txt文件内容问题
Nov 07 Javascript
小程序实现投票进度条
Nov 20 Javascript
vue实现在线预览pdf文件和下载(pdf.js)
Nov 26 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 CLI模式下的多进程应用分析
2013/06/03 PHP
PHP实现的封装验证码类详解
2013/06/18 PHP
简单分析ucenter 会员同步登录通信原理
2014/08/25 PHP
PHP多个文件上传到服务器实例
2014/10/29 PHP
PHP统计当前在线用户数实例讲解
2015/10/21 PHP
CentOS下PHP7的编译安装及MySQL的支持和一些常见问题的解决办法
2015/12/17 PHP
Symfony2开发之控制器用法实例分析
2016/02/05 PHP
php正则去除网页中所有的html,js,css,注释的实现方法
2016/11/03 PHP
使用jQuery的ajax功能实现的RSS Reader 代码
2009/09/03 Javascript
js获得地址栏?问号后参数的方法
2013/08/08 Javascript
Js参数值中含有单引号或双引号问题的解决方法
2013/11/06 Javascript
jQuery获得内容和属性示例代码
2014/01/16 Javascript
JavaScript初学者建议:不要去管浏览器兼容
2014/02/04 Javascript
js实现简单的购物车有图有代码
2014/05/26 Javascript
jQuery插件jPaginate实现无刷新分页
2015/05/04 Javascript
轻量级jQuery插件slideBox实现带底栏轮播(焦点图)代码
2016/03/28 Javascript
jquery利用json实现页面之间传值的实例解析
2016/12/12 Javascript
node文字生成图片的示例代码
2017/10/26 Javascript
vue实现验证码输入框组件
2017/12/14 Javascript
详解使用webpack+electron+reactJs开发windows桌面应用
2019/02/01 Javascript
[00:32]2018DOTA2亚洲邀请赛出场——VP
2018/04/04 DOTA
[31:55]完美世界DOTA2联赛循环赛 IO vs GXR BO2第一场 11.04
2020/11/05 DOTA
操作Windows注册表的简单的Python程序制作教程
2015/04/07 Python
Python数据类型详解(四)字典:dict
2016/05/12 Python
Python编程之字符串模板(Template)用法实例分析
2017/07/22 Python
python-xpath获取html文档的部分内容
2020/03/06 Python
Django封装交互接口代码
2020/07/12 Python
临床护理求职信
2014/04/26 职场文书
行政部经理助理岗位职责
2014/06/15 职场文书
消防宣传口号
2014/06/16 职场文书
2014小学教师年度考核工作总结
2014/12/03 职场文书
2015公司年度工作总结
2015/05/14 职场文书
毕业生登记表班级意见
2015/06/05 职场文书
大学生奶茶店创业计划书
2019/06/25 职场文书
《妈妈别哭,有我在》读后感3篇
2020/01/13 职场文书
Python入门之使用pandas分析excel数据
2021/05/12 Python