Vue组件继承 -- 1
公司后台系统使用了 Vue + ElementUI, 使用 TypeScriptElementUI 简单,但是还不够简单,就想写一些比较简单的和公司业务有关联的组件;最简单的,例如 Autocomplete 组件,例如,基于某一个类型的 Autocomplete 组件,用法如下:<EhayAutocomplete entityTypeName="EH_Client"
公司后台系统使用了 Vue + ElementUI, 使用 TypeScript
ElementUI 简单,但是还不够简单,就想写一些比较简单的和公司业务有关联的组件;
最简单的,例如 Autocomplete 组件,
例如,基于某一个类型的 Autocomplete 组件,用法如下:
<EhayAutocomplete entityTypeName="EH_Client" v-model="client"></EhayAutocomplete>
简单的用法,好办:
自定义一个组件,里面包含有 elautocomplete 组件,然后写属性,写代码,轻松搞定;
<template>
<el-autocomplete :fetchSuggestions="fetchSuggestions"></el-autocomplete>
</template>
<script type='ts'>
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { FetchSuggestions, FetchSuggestionsCallback } from "element-ui/types/autocomplete";
@Component
export default class extends Vue {
@Prop()
entityTypeName: string;
@Prop()
fetchSuggestions: FetchSuggestions;
。。。。。。。。 实体查询的代码
}
</script>
。。。。。
但是,很快问题来了,我的组件需要增加 placeholder 属性,这个也很见简单。。。
<template>
<el-autocomplete :placeholder="placeholder" :fetchSuggestions="fetchSuggestions"></el-autocomplete>
</template>
<script type='ts'>
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { FetchSuggestions, FetchSuggestionsCallback } from "element-ui/types/autocomplete";
@Component
export default class extends Vue {
@Prop()
entityTypeName: string;
@Prop()
placeholder: string;
@Prop()
fetchSuggestions: FetchSuggestions;
}
</script>
但是,很快问题来了,我的组件需要添加 class 属性,
于是,这样似乎没完没了了。。。因为基本上 el-autocomplete 里面的所有属性都需要一五一十的添加到我自定义的组件里,然后再添加到自定义的组件所包含的 el-autocomplete 里;
这还只是一个 autocomplete 组件,还有一堆的其他的 element-ui 组件
很明显,component模式不是一个好的想法;因为对于包裹的组件,我们需要暴露包裹的组件的每一个属性和方法以及事件,这样一想,当然使用继承更好;
最简单的,当然就是下面这样,
export default class EhayAutoComplete extends elementUI.Autocomplete {
}
可惜,楼上这样的写法是错误的。。正确的写法是像下面这样:
export default class EhayAutoComplete extends Vue.component('ElAutocomplete') {
}
但是,郁闷的是,我们需要覆盖 ElAutocomplete 组件里面的 fetchSuggestions 属性,这样我们才能够使用
<EhayAutocomplete entityTypeName="EH_Client" v-model="client"></EhayAutocomplete> 这样的方式,
然后我们的组件覆盖 fetchSuggestions 属性,让这个属性所对应的函数 能够根据 entityTypeName 去从服务器后台查询 客户列表,并显示在 Autocomplete 的下拉列表中
好吧,这个也不难,因为 fetchSuggestions 是 ELAutocomplete 定义的 Prop, 我们不能直接给这个 Prop 赋值,(要强制赋值貌似也可以,vue也就给一个警告而已),但是对于强迫症的俺而言,警告当然是不能接受的;
于是,不能直接给 fetchSuggestions prop 赋值,那我自己定义一个 fetchSuggestions 属性,
像下面这样,
export default class EhayAutoComplete extends Vue.component('ElAutocomplete') {
fetchSuggestions: FetchSuggestions;
created() {
this.fetchSuggestions = (key: string , cb) => {
let ary := [{ value: 'abc'}, { value: 'def' }];
cb(ary);
};
}
}
测试一下,貌似能够工作,但是,稀奇的事情发生了:
如果 autocomplete 没有内容时,点击 autocomplete 将显示 abc 和 def 两个下拉选项
但如果选中 abc 这一项,再次点击,居然就告诉我说 fetchSuggestions 函数没有定义;
看起来,感觉上 el-autocomplete 在没有内容时,使用的是 this.fetchSuggestions 进行填充数组的查询;
但, el-autocomplete 选中了内容时,使用的是 this.$props['fetchSuggestions'] 来进行填充数组的查询;
稀奇的处理方式。。。
这样,我们的问题是,我们需要继承 EL-autocomplete 组件,但是还必须能够覆盖其中的 Props 定义。。。
这个也不难。。。
export default class EhayAutoComplete extends Vue.component('ElAutocomplete') {
/** 实体类型名称 */
@Prop({required: true})
entityTypeName: string;
/** 查询实体时可能还需要的父实体Id */
@Prop({ default: ''})
parentEntityId: string;
@Prop({
default: () => (key: string, cb: FetchSuggestionsCallback) => {
let vthis = this as any;
vthis.remoteQuery(key, cb);
}
})
fetchSuggestions: FetchSuggestions;
。。。。。。。。。。。 定义和 entityTypeName 有关系的从服务器查询实体的方法
remoteQuery。。。。。
}
上面的代码默认会报编译错误,因为 typescript 如果启用 strict 模式,noImplicitThis 为 true,
tsconfig.json 里面的 noImplicitThis 编译选项需要改为 false, 才能够使 this 可以为 any 类型;
看上去貌似没有问题;
但熟悉 TypeScript 的同学都知道,这儿有一个隐藏的 this 问题;
http://blog.darkthread.net/post-2016-04-23-typescript-this-pitfall-in-debug.aspx 这儿有详细说明,
TypeScript 太智能了,它自动把 this 移到了外面,这个时候的 this
将指向 {
default: () => (key: string, cb: FetchSuggestionsCallback) => {
let vthis = this as any; // as any; //this as AbstractEntityQueryComponent;
vthis.remoteQuery(key, cb);
}
}
这个对象,而不是我们想要的 EhayAutoComplete 组件的实例;
所以,这个时候会报一个脚本错误,也就是 remoteQuery 未定义错误;
这儿的悲催问题是,在 {
default: () => (key: string, cb: FetchSuggestionsCallback) => {
let vthis = this as any;
vthis.remoteQuery(key, cb);
}
}
这个对象内,我们没有办法访问直接或者间接访问 EhayAutoComplete 的当前实例,
悲催的是,调试一下,设个断点, this 明明指向了 EhayAutoComplete 的当前实例,
但是 typescript 编译器产生的代码就变成了 _this 变量;
还好,typescript 还是留了一道小口子,
我们改成 let vthis = eval('this'); 就可以访问到调用这个函数的 this 对象,也就是 EhayAutoComplete 的当前实例,
export default class EhayAutoComplete extends Vue.component('ElAutocomplete') {
/** 实体类型名称 */
@Prop({required: true})
entityTypeName: string;
/** 查询实体时可能还需要的父实体Id */
@Prop({ default: ''})
parentEntityId: string;
@Prop({
default: () => (key: string, cb: FetchSuggestionsCallback) => {
// tslint:disable-next-line:no-eval
let vthis = eval('this');
vthis.remoteQuery(key, cb);
}
})
fetchSuggestions: FetchSuggestions;
。。。。。。。。。。。 定义和 entityTypeName 有关系的从服务器查询实体的方法
remoteQuery。。。。。
}
很好,组件基本完成,但是这样就够了吗??当然不够啦;
实体查询的代码,很明显,不但可以用于 autocomplete 还可以用于其他的地方,例如 select 之类的组件,所以,我们需要把实体查询的代码从 EhayAutoComplete 中抽离出来,这样我们的任务是,
我们的组件需要继承 el-autocomplete 以及 我们自己的 entity-query 两个类
Vue实现这个也不难,因为它有 mixins 方法,可以混合2个类的属性、方法;
于是把实体查询的代码抽离出来,
这里面 WebApi, EhayModel 之类的都是根据服务器后台接口自动产生的 typescript 代码;
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { WebApi } from "../../api/auto_actions";
import * as EhayModel from "../../api/auto_interface";
import { ArrayUtil } from "../../utils/BrowserExtension";
import { FetchSuggestions, FetchSuggestionsCallback } from "element-ui/types/autocomplete";
/**
* 定义实体查询的组件,
* 包含和实体查询有关的方法;
**/
@Component
export default class AbstractEntityQueryComponent extends Vue {
/** 实体类型名称 */
@Prop({required: true})
entityTypeName: string;
/** 查询实体时可能还需要的父实体Id */
@Prop({ default: ''})
parentEntityId: string;
@Prop({
default: () => (key: string, cb: FetchSuggestionsCallback) => {
// tslint:disable-next-line:no-eval
let vthis = eval("this") as any; // as any; //this as AbstractEntityQueryComponent;
vthis.remoteQuery(key, cb);
},
})
fetchSuggestions: FetchSuggestions;
/**
* 查询函数
* @param key 实体名称关键字
* @param callback 回调函数,对于 autocomplete 组件有用;
*/
private queryFunc: (key: string, callback?: any) => Promise<Array<EhayModel.LabelAndValue>> = (key: string, callback?: any) => new Promise <Array< EhayModel.LabelAndValue >>((resolve) => []) ;
/**
* 重新计算查询函数;
*/
recalateQueryFunc() {
this.queryFunc = (key: string) => new Promise<EhayModel.LabelAndValue[]>((resolve, reject) => {
WebApi.EntityQuery.getEntityListByKey(this.entityTypeName, key, this.parentEntityId)
.then((data) => resolve(data.data));
});
}
/**
* onMounted 消息函数
* 给 entityTypeName、parentEntityId 2个属性设置观察函数;
*/
mounted() {
this.$watch("entityTypeName", () => this.recalateQueryFunc(), { immediate: true });
this.$watch("parentEntityId", () => this.recalateQueryFunc(), { immediate: true });
}
/** 是否在加载数据 */
loading: boolean = false;
/** 可用的实体数组 */
entityArray: EhayModel.LabelAndValue[] = [];
/** 远程查询
* @param key 实体名称关键字
* @param callback 回调函数,对于 autocomplete 组件有用;
*/
remoteQuery(key: string, callback?: any) {
this.loading = true;
setTimeout(() => {
this.queryFunc(key).then((data) => {
this.loading = false;
ArrayUtil.clearAndCopy(this.entityArray, data);
if (callback) {
callback(data);
}
});
}, 50);
}
}
再修改一下组件定义,混合 elementUI.Autocomplete 和 AbstractEntityQueryComponent 两个类定义,
终于,大功告成。。
import AbstractEntityQueryComponent from '../AbstractEntityQueryComponent';
import { Component, Prop, Watch, Mixins } from "vue-property-decorator";
import Vue, { PropOptions } from 'vue';
import elementUI from "element-ui";
/**
* 自动完成组件
* 使用方法,继承自 elementUI.Autocomplete,
* <AutoComplete entityTypeName='EH_Client' v-model='client'></AutoComplete>
*/
@Component({})
export default class EhayAutoComplete extends Mixins(elementUI.Autocomplete, AbstractEntityQueryComponent) {
}
更多推荐
所有评论(0)