前言
有个项目,需要使用 mixins 来管理一些通用方法。同时该项目使用 Typescript。
问题
编译器报错,提示组件中没有该 mixin 中的内容。具体看一下代码。
MixinA:
export const MixinA = {
method: {
sayHello() {
// ...
}
}
}
component:
export default Vue.extend({
mixins: [MixinA],
create() {
this.sayHello(); // <- 报错位置.
}
})
报错:
Property 'sayHello' does not exist on type 'CombinedVueInstance<Vue, unknown, unknown, unknown, Readonly<Record<never, any>>>'.Vetur(2339)
根据报错信息,可以看到是 Vetur 这个插件报的错。
究其原因,还是因为 TypeScript 不够聪明,没法知道 Mixin 到底是什么样的。
解决方案
这里有许许多多种,我收集整理一下。
-
简单粗暴法 (as 强制转换) 极度不推荐
跟着上面的内容:
(this as any).sayHello();
报错消失,但是这样会让代码很脏,组件阅读性差的同时,还要写很多 as 转换,同时,转换 any 让 typeScript 的意义荡然无存,还不如不用。
-
继承 mixins (只适合当个 mixin)
mixinA:
export const MixinA = Vue.extend({ method: { sayHello() { // ... } } })
components:
export default MixinA.extend({ mixins: [MixinA], create() { this.sayHello(); } })
也不报错了,通过继承 mixinA 的方式,让编译器知道里面有什么东西。
缺点:违背 mixins 的设计初衷,mixins 本身就支持多 mixin 混入。
优点:简单粗暴。
-
使用 vue-typed-mixins
import Vue from 'vue' import mixins from 'vue-typed-mixins' const mixinsA = Vue.extend({ data() { return { foo: 'test' } } }) const mixinsB = Vue.extend({ data() { return { bar: 123 } } }) const App = mixins(mixinsA, mixinsB).extend({ data() { return { value: true } }, computed: { concat(): string { return `${this.foo} ${this.bar} ${this.value}` } } })
优点:可以混合,多个 mixin
缺点:使用习惯的修改。 还是比较支持的。 -
升级为 Vue3。
Vue 官方在 3 后,有为 mixin 做的处理。(具体没有了解过,虽然很吐槽这种升级的方式) -
遵循 Vue 和 TS 原则的一种解决方式.
mixinA :
// mixinA.vue 导出默认 Vue.extend({…})
mixinB:
// mixinB.vue 导出默认 Vue.extend({…})
那么使用这两个的组件可以定义为:
export default (Vue as VueConstructor<Vue & InstanceType<typeof MixinA>& InstanceType<typeof MixinB>>).extend({ mixins: [MixinA, MixinB],
-
自己写接口
interface mixinState { sayHello: () => {} } export default (Vue as VueConstructor<Vue & mixinState>).extend({ name: 'Home', create() { this.sayHello(); } })
本质上与 第 5 种 是一样的。
缺点:要写许多接口。 -
使用 vue-property-decorator 等。
使用类型注解的方式。没研究,不喜欢这种写法。