Vue组件继承 -- 3
https://blog.csdn.net/hesi726/article/details/83115235 这儿说了我们 Wrap 一个 Vue组件后想要实现的3个目标:也就是说此组件应该和被 wrap 的组件一样被另外的组件使用;但是又能够在需要时由此组件向被 Wrap 的组件设置数据源;例如 ElementUi 里面的 el-select , 继承的话,我们就无法给此组件内部设置...
https://blog.csdn.net/hesi726/article/details/83115235 这儿说了我们 Wrap 一个 Vue组件后想要实现的3个目标:
也就是说此组件应该和被 wrap 的组件一样被另外的组件使用;但是又能够在需要时由此组件向被 Wrap 的组件设置数据源;
例如 ElementUi 里面的 el-select , 继承的话,我们就无法给此组件内部设置下拉菜单的数据源;
1. 组件需要能够自动把父组件传递的 prop 传递给里面的 el-input ;
就是 <EhayInput :max="10"></EhayInput> 这样写时, EhayInput 里面的 el-input 也应该自动绑定 max=10
这个在文章中已经给出了 https://blog.csdn.net/hesi726/article/details/83115235 了,
2. 自动向上传递事件
也就是里面 el-input 的 focus 之类的事件应该会自动传递到 EhayInput , 类似于这样 <EhayInput @focus="focus"></EhayInput>
这个有点麻烦,因为我们无法得知子组件会发送什么样的消息,
但是,我们可以修改一下 Vue, 默认提供的 $emit 方法,使其发送我们自定义的消息;
static addAllEventHandle_Emit(wrapComponent: Vue) {
let inner: Vue = wrapComponent.$refs['innerOriginal'] as Vue;
(inner as any).$originalEmit = inner.$emit; //修改 默认的 $emit 方法
inner.$emit = (event, ...args) => {
return ((inner as any).$originalEmit)('wrapEvent', event, ...args); //总是发送自己的消息
}
inner.$on('wrapEvent', (event: string, ...args: any[]) => {
wrapComponent.$emit(event, ...args);
});
}
我们外部 wrap 的组件只需要监听我们自定义的消息,并向上转发子组件实际发送的消息就可以了;
3. 自动绑定 slot,
这个就比较麻烦了,麻烦的地方在于 vue 里面并没有一个可以查询到 vue组件的所有 slot;
$slot 和 scopedSlot 只有在外部传入了 slot 的内容时,才能够在 mounted 事件中访问到;
也就是说,通过 vue组件的属性,我们无法自动添加 slot 的定义来从外部组件接收 slot 内容并传送给子组件;
最后使用了一个野蛮的方法。。。
分析子组件 render 函数的文本内容,从中猜测可能时 slot 的部分;
static wrapAndCompile(bewrapedComponent: any , assignSlotContent: Record<string, string> = {}): (createElement: any) => VNode {
//alert(c)
let slotsString = "";
let renderString: string = '' + bewrapedComponent.render.toString(); //从组件的 render 函数中获取 slots 的定义;
let map: Set<string> = new Set<string>();
let pos = renderString.indexOf(`_t("`);
while (pos >= 0) {
let posEnd = renderString.indexOf(`"`, pos + 4);
let slotName = renderString.substring(pos + 4, posEnd);
map.add(slotName);
pos = renderString.indexOf(`_t("`, posEnd + 1);
}
let slotsInRender = map.values();
let getSlotContent = (key: string) => assignSlotContent[key] || "";
map.forEach((key) => {
let slotContent = getSlotContent(key);
if (key === "default") { return; } //默认的槽不输出;
slotsString += `<template slot="${key}">
<slot name="${key}">${slotContent}</slot>
</template>`;
});
console.log(slotsString);
let result: string = '';
result = "<" + bewrapedComponent.name + " ref='innerOriginal' ";
for (let prop in bewrapedComponent.props) { // 绑定组件中的属性
result = result + ' :' + prop + "='" + prop + "' ";
}
result += ">";
result += slotsString;
assignSlotContent = assignSlotContent || [];
/*for (let index in assignSlotContent) {
let slot = assignSlotContent[index];
if (slot === "" || slot === "default")
result = result + "<slot></slot>"; //默认槽;
else result = result + "<slot name='" + slot + "'></slot>";
}*/
let defSlotContent = getSlotContent('default');
result += defSlotContent;
result += "</" + bewrapedComponent.name + ">";
console.log("*********************************");
console.log(result);
return Vue.compile(result).render;
}
看上去还不错。。。
好,现在我们实现了自己的目标了;
给出所有的代码:
VueComponentWrapperAssistant 工具类
import Vue, { VNode } from "vue";
/** 带有名称的接口 */
interface IName {
name: string;
}
/** 带有名称的 Vue 接口 */
interface VueAndName extends Vue {
name: string;
}
/**
* Vue组件的Wrap助手类
*/
export default class VueComponentWrapperAssistant {
/**
* 根据组件定义产生 此Wrapp此组件的定义的 template 字符串,并通过 Vue编译后,产生 render 函数;
* 例如,产生类似于 <ElInput ref='innerOriginal' :value='value' :size='size' :resize='resize' :form='form' :disabled='disabled' :readonly='readonly' :type='type' :autosize='autosize' :autocomplete='autocomplete' :autoComplete='autoComplete' :validateEvent='validateEvent' :suffixIcon='suffixIcon' :prefixIcon='prefixIcon' :label='label' :clearable='clearable' :tabindex='tabindex' ></ElInput>
* 的字符串,并使用 Vue.compile() 编译此字符串后,返回编译后的 render 函数;
* @param bewrapedComponent 被封装的ElementUI 组件的实例,好稀奇,
* 传入 elementUI.Input 应该是一个 class 定义, 但是却传入了 类实例;
* @param assignSlotContent 指定的槽内容,例如对于我们自定义的 EhaySelect 组件,应该给 slot default 设置默认的槽内容;
*/
static wrapAndCompile(bewrapedComponent: any , assignSlotContent: Record<string, string> = {}): (createElement: any) => VNode {
//alert(c)
let slotsString = "";
let renderString: string = '' + bewrapedComponent.render.toString(); //从组件的 render 函数中获取 slots 的定义;
let map: Set<string> = new Set<string>();
let pos = renderString.indexOf(`_t("`);
while (pos >= 0) {
let posEnd = renderString.indexOf(`"`, pos + 4);
let slotName = renderString.substring(pos + 4, posEnd);
map.add(slotName);
pos = renderString.indexOf(`_t("`, posEnd + 1);
}
let slotsInRender = map.values();
let getSlotContent = (key: string) => assignSlotContent[key] || "";
map.forEach((key) => {
let slotContent = getSlotContent(key);
if (key === "default") { return; } //默认的槽不输出;
slotsString += `<template slot="${key}">
<slot name="${key}">${slotContent}</slot>
</template>`;
});
console.log(slotsString);
let result: string = '';
result = "<" + bewrapedComponent.name + " ref='innerOriginal' ";
for (let prop in bewrapedComponent.props) { // 绑定组件中的属性
result = result + ' :' + prop + "='" + prop + "' ";
}
result += ">";
result += slotsString;
assignSlotContent = assignSlotContent || [];
/*for (let index in assignSlotContent) {
let slot = assignSlotContent[index];
if (slot === "" || slot === "default")
result = result + "<slot></slot>"; //默认槽;
else result = result + "<slot name='" + slot + "'></slot>";
}*/
let defSlotContent = getSlotContent('default');
result += defSlotContent;
result += "</" + bewrapedComponent.name + ">";
console.log("*********************************");
console.log(result);
return Vue.compile(result).render;
}
/**
* 给 wrapComponent 里面的 ref 名称为 innerOriginal (此组件再 wrapAndCompile 函数中创建)的组件添加事件的 emit;
*/
static addAllEventHandle_Emit(wrapComponent: Vue) {
let inner: Vue = wrapComponent.$refs['innerOriginal'] as Vue;
(inner as any).$originalEmit = inner.$emit; //修改 默认的 $emit 方法
inner.$emit = (event, ...args) => {
return ((inner as any).$originalEmit)('wrapEvent', event, ...args); //在定义一个消息
}
inner.$on('wrapEvent', (event: string, ...args: any[]) => {
wrapComponent.$emit(event, ...args);
});
}
}
AbstractEntityQueryComponent 实体查询
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";
import { QueryChangeHandler } from "element-ui/types/select";
/**
* 定义实体查询的组件,
* 包含和实体查询有关的方法;
* 不要创建和本类相关的实例;
**/
@Component
export default class AbstractEntityQueryComponent extends Vue {
/** 实体类型名称 */
@Prop({required: true})
entityTypeName: string;
/** 查询实体时可能还需要的父实体Id */
@Prop({ default: ''})
parentEntityId: string;
/** value 属性,此书属性用于初始化的查询, 需要注意一个事实是,如果 label 和 value 具有不同的值时,
* 我们只需要绑定 value , 但此时将显示 value,
* 这不是我们想要的,我们需要的是 label;
* 所以此处需要添加一个 value 属性,以便传入到后台进行查询;
*/
@Prop({ default: ''})
value: string;
/** 覆盖 autocomplete 中的 fetchSuggestions prop*/
@Prop({
default: () =>
/**
* @param key: 查询关键字,
* @param cb: 在 autocomplete 里面对查询的回调;
*/
(key: string, cb: FetchSuggestionsCallback) => {
// tslint:disable-next-line:no-eval
let vthis = (eval("this") as any).$parent;
vthis.innerRemoteQuery(key, cb);
},
})
fetchSuggestions: FetchSuggestions;
/** 覆盖 sselect 中的 remote-method prop*/
@Prop({
default: () => (key: string) => {
// tslint:disable-next-line:no-eval
let vthis = (eval("this") as any).$parent;
vthis.innerRemoteQuery(key);
},
})
remoteMethod: QueryChangeHandler;
/**
* 查询函数
* @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));
});
}
/**
* mount 消息回调函数
* 给 entityTypeName、parentEntityId 2个属性设置观察函数;
*/
mounted() {
this.$watch("entityTypeName", () => this.recalateQueryFunc(), { immediate: true });
this.$watch("parentEntityId", () => this.recalateQueryFunc(), { immediate: true });
this.recalateQueryFunc();
//给定值发生变化时,根据值去加载名称;
this.$watch("value", () => () => { this.queryEntityArrayAccordValue(); }, { immediate: true});
this.queryEntityArrayAccordValue();
}
/** 根据初始值查询 名称和值 数组 */
queryEntityArrayAccordValue() {
if (this.value && this.entityArray.findIndex(a=> a.value == this.value) < 0) {
WebApi.EntityQuery.getEntityListById(this.entityTypeName, this.value).then((data) => {
ArrayUtil.clearAndCopy(this.entityArray, data.data);
});
}
}
/** 是否在加载数据 */
isQuerying: boolean = false;
/** 查询后的实体数组 */
entityArray: EhayModel.LabelAndValue[] = [];
/** 远程查询
* @param key 实体名称关键字
* @param callback 回调函数,对于 autocomplete 组件有用;
*/
innerRemoteQuery(key: string, callback?: any) {
this.isQuerying = true;
setTimeout(() => {
this.queryFunc(key).then((data) => {
this.isQuerying = false;
ArrayUtil.clearAndCopy(this.entityArray, data);
if (callback) {
callback(data);
}
});
}, 50);
}
}
自定义的封装 el-select 组件的组件
import AbstractEntityQueryComponent from '../AbstractEntityQueryComponent';
import { Component, Prop, Mixins } from "vue-property-decorator";
import elementUI from "element-ui";
import VueComponentWrapperAssistant from "../VueComponentWrapperAssistant";
/**
* 下拉选择组件,默认单选, 继承自 ElementUI.Select
* <EhayEntitySelect entityTypeName='EH_Client' v-model='client'></EhayEntitySelect>
*/
@Component({
render: VueComponentWrapperAssistant.wrapAndCompile(elementUI.Select, {
// 给定 默认的 slot 定义
'default': `<el-option
v-for="item in entityArray"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
`}),
})
export default class EhayEntitySelect extends Mixins(elementUI.Select, AbstractEntityQueryComponent) {
/** 覆盖 sselect 中的 filterable prop,使其默认为 true*/
@Prop({ default: true })
filterable: boolean;
/** 覆盖 sselect 中的 remote prop,使其默认为 true*/
@Prop({ default: true })
remote: boolean;
/** 覆盖 sselect 中的 clearable prop,使其默认为 true*/
@Prop({ default: true })
clearable: boolean;
/** 作为 value 唯一标识的键名,绑定值为对象类型时必填 */
@Prop({ default: "value"})
valueKey: string;
/**
* mounted 消息回调函数
* 添加消息的向上传递;
*/
mounted() {
VueComponentWrapperAssistant.addAllEventHandle_Emit(this);
}
}
如何使用?
就像普通的 el-select 组件一样使用,
<EhayEntitySelect v-model="ehayEntitytSelect_Value" entityTypeName="OA_User">
</EhayEntitySelect>
更多推荐
所有评论(0)