公司后台系统使用了 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) {
}




 

 

 

Logo

前往低代码交流专区

更多推荐