众所周知,在 vue中,如果想定义一个全局变量的方法很简单,直接在 vue的原型上挂载属性或者方法即可。
但是,加上了typescript之后, Vue.prototype.$xxx = xxx 这种挂载方式就不行了。无论在哪里都访问不了挂载的内容。Vue原型上也没有。那怎么办呢?
第一种方式(推荐):插件
官方文档在 TypeScript 支持 这一项中的 增强类型以配合插件使用 表示了可以用插件的方式来定义全局变量,然后用 xxx.d.ts 这种文件来声明类型。
那就开始开发插件:官方开发插件说明
插件最重要的就是 install 方法。举个最简单的例子:
testInstall.ts
const protoInstall = { install: (Vue:any,options:any) => { Vue.prototype.$install = function(){ console.log('install') }; Vue.prototype.$testData = 'testData'; } } export default protoInstall;
(1)不要说我Vue:any,options:any 这种写法,因为我不知道这两个的类型到底是什么ヾ(。 ̄□ ̄)ツ゜゜゜
main.ts
import hhInstall from './assets/js/testInstall';
Vue.use(hhInstall);
使用的时候:
结果:
(1)可以看到,虽然报未找到该属性,或者该方法的错误,但是不影响结果。
(2)那怎么解决这个问题呢?这时就需要添加 声明文件了。
解决:
(1)在src 下新建一个 xxx.d.ts 文件 。我是在src下再新建一个type文件夹,然后再把刚才的声明文件放到 这文件夹中,方便管理。
(2)xxx 名称可以随便写,反正我写的 vue-prototype.d.ts 更清晰一点。
vue-prototype.d.ts
declare module "vue/types/vue" {
interface Vue {
$testData:string;
$install:Function;
}
}
保存后,如果未生效,再重新启动一下项目就行。然后就有类型提示了。以后如果还需要添加全局属性或者方法,在插件里面挂载之后,声明文件里面再添加类型说明。
不过我有不明白的地方:
(1) 官方文档说,确保在声明补充的类型之前导入 'vue'。我的声明文件 vue.prototype.d.ts 并未导入vue,但是没问题。为什么呢?难道是我用vue-cli3 脚手架搭建的项目,shims-vue.d.ts 这个文件已经导入就不用了吗?
(2) 这种 xxx.d.ts 文件,vue内部是怎么识别的?又是怎么处理的呢?
第二种方法:mixin混入
由于是想全局定义属性和方法,那么mixin也能实现。比如:
main.ts
const vueMixins = { data(){ return { $testData:'mixin testData', } }, methods:{ $install:function(){ console.log('mixin install') } } } Vue.mixin(vueMixins)
使用:
结果:
可以看到,定义的属性 $testData 是undefined。而去看vue实例,发现挂载到 $data 里面的。但是为什么访问不了,而只有 通过 this.$data.$testData 来访问?这个我也不太清楚
相同的,出现属性不存在的问题,还是要添加 声明文件来说明一下。
用这种方式,如果定义一个构造函数在data里面,后面的方法都没提示。不太方便。
最后贴上我自己的全局事件总线代码,就是为了不想用的时候还要引入,才知道了 typescript 不能直接定义全局变量的问题
ヾ(゚∀゚ゞ)
如果不想全局引入,那就不搞成插件的方式就行了,直接export default class xxx。
代码比较垃圾,轻喷。
assets/js/eventBus.ts
/** * @desc 全局事件总线 */ /** * 事件信息接口 */ interface EventItem { name:string; fun:Function; } /** * 判断是否存在已经绑定的事件接口 */ interface JudgeStatus{ status:boolean; index:number } export class EventBusHandler{ private EventArr:EventItem[] = []; private static _instance: EventBusHandler; public static get instance(): EventBusHandler { if (!EventBusHandler._instance) { EventBusHandler._instance = new EventBusHandler(); } return EventBusHandler._instance; } /** * 判断是否已经存在注册的事件 * @param eventName 事件名 */ private judgeHadEventAlready(eventName: string):JudgeStatus{ let status = false; let pos = -1; this.EventArr.forEach((val,index) => { if(val.name === eventName){ status = true; pos = index; } }) return { status:status, index: pos } } /** * 事件监听 * @param eventName 事件名 * @param func 回调函数 */ public on(eventName: string, func: Function) { const statusTarget = this.judgeHadEventAlready(eventName); // 如果未监听过则添加进去 if (!statusTarget.status) { this.EventArr.push({ name:eventName, fun:func }) } } /** * 事件触发 * @param eventName 事件名 * @param arg 传入的参数。如果想传多个参数,可把 arg:any 换成 ...arg:any[] */ public emit(eventName: string, arg:any) { const statusTarget = this.judgeHadEventAlready(eventName); if(statusTarget.status){ const func = this.EventArr[statusTarget.index].fun; this.EventArr[statusTarget.index].fun = func; func(arg); }else{ console.warn('暂未监听:'+eventName+ '事件'); } } /** * 事件移除 * @param eventName * @param func */ public remove(eventName: string) { const statusTarget = this.judgeHadEventAlready(eventName); if(statusTarget.status){ this.EventArr.splice(statusTarget.index,1); }else{ console.warn('未监听:'+eventName+ '事件'); } } } const EventBusFunc = { install: (Vue:any,options:object) => { Vue.prototype.$eventBus = EventBusHandler.instance; } } export default EventBusFunc;
声明文件:
import {EventBusHandler} from '@/assets/js/eventBus'; declare module "vue/types/vue" { interface Vue { $eventBus:EventBusHandler; } }
main.ts 引入:
import EventBus from './assets/js/eventBus';
Vue.use(EventBus);
使用:
this.$eventBus.on('func',() => {}) this.$eventBus.emit('func','1111') this.$eventBus.remove('func')
果然还是实践出真知,网上的大部分答案都不靠谱 (⊙﹏⊙)