二次封装组件时,$attrs和$listeners很有用
//使用时,外部属性和方法可以和使用el-image时一致,内部没对应的props属性 都在$attrs里
//$listeners 包含了父作用域中的 (不含 `.native` 修饰器的) `v-on` 事件监听器
<template>
<div id="CustomImage">
<el-image v-bind="$attrs" v-on="$listeners">
<div slot="error" class="image-slot">
<img :src="require('image-f/icon-empty-img.png')" alt="图片加载失败.png"/>
</div>
<div slot="placeholder" class="placeholder-slot">加载中...</div>
</el-image>
</div>
</template>
vue2组件设计的一些技巧
- 以下为研究elementui源码 摘出的
动态类名
//数组中 每一项都是js代码,产出字符串/vue规定的对象值为true 即赋予
:class="[ type ? 'el-button--' + type : '', buttonSize ? 'el-button--' + buttonSize : '', type ? `el-link--${type}` : '', { 'is-disabled': buttonDisabled, 'is-loading': loading, 'is-plain': plain, 'is-round': round, 'is-circle': circle } ]"
插槽外层标签元素是否存在
<span v-if="$slots.default">
<slot></slot>
</span>
检测是否传入某个props
Object.prototype.hasOwnProperty.call(this.$options.propsData,'disabled')
provide inject保持响应式(推荐传对象)
摘自:cdn链接
一行多个重复元素 中间需要间距
.el-button + .el-button {
margin-left: 10px;
}
引入css
- 官网建议 main.js 引入 import 'element-ui/lib/theme-chalk/index.css';
- 实际上,该文件是把所有组件的css全堆到一个文件里(不是@includes引入 是css源码堆一块)13459行
line-height: 1;
- 设置完font-size后 若想行高和font-size一致 如此便可,没必要写确切的px数值
不确定对象是否有某属性
- (this.elFormItem || {}).elFormItemSize
- this.elFormItem?.elFormItemSize (推荐) 和上面效果一样,若this.elFormItem不存在,返回的是undefined
事件发送与监听
两个基础概念
- 组件内$emit,使用组件时 @事件名="func"
- eventbus 借助同一个vue实例,一个在上面$emit 另一个在上面$on监听
- 面对的情况
- 单选是通过插槽的方式在单选组内的,把单选里触发的事件暴露为单选组的事件
//radio-button
<input v-model="value"/>
computed: {
value: {
get() {
return this._radioGroup.value;
},
set(value) {
//和 ElRadioGroup 组件内部this.$emit一样
this._radioGroup.$emit('input', value);
}
},
_radioGroup() {
let parent = this.$parent
while (parent) {
// 往外层遍历,直到外层组件为ElRadioGroup 或者没有外层为止
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent
} else {
return parent
}
}
return false
},
},
实际上 有两个地方都能监听到
- 使用ElRadioGroup组件时,<el-radio-group @input="func">
- ElRadioGroup组件内部,mounted里用 this.$on('input',(val)=>{})
其实组件$emit时,组件本身也会成为eventbus的载体,插槽里的子组件向祖先传值 也可以考虑这种方法。element是直接把事件
element封装的跨组件传事件的方法
- src/mixins/emitter.js
// 向后代组件 传事件并携带参数
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
// 向上逐级找componentName匹配的,并用匹配到componentName的组件emit事件
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
使用模式
- broadcast 向后代发消息
祖先
import Emitter from '@/mixins/emitter.js'
export default {
mixins: [Emitter],
methods: { //通用的往后代发事件的方法
sendToSon() {
this.broadcast('HzItem', 'ancestorSound', '俺是恁祖宗')
},
-----------------------------------------------
后代
mounted() {
this.$on('ancestorSound', this.ancestorSound)
},
methods: {
ancestorSound(val) {
alert(val, '子组件收到的信息')
},
- dispatch向祖先发消息
祖先
//radio-group组件
//该组件使用时 <radio-group @handleChange="func">也能触发
created() {
//可以把祖先组件当成eventbus载体 $on监听该事件,也可以直接在外面监听该事件
this.$on('handleChange', value => {
this.$emit('change', value);
});
},
-------------------
后代
//radio-button组件
import Emitter from '@/mixins/emitter.js'
mixins: [Emitter],
methods: {
handleChange() {
this.$nextTick(() => {
//该方法是借助找到的 祖先vue实例.$emit('handleChange',this.value)
this.dispatch('ElRadioGroup', 'handleChange', this.value);
});
}
}
element设计该事件传值模式,其实是基于eventbus,但 实例.$emit 既可以是借助该实例搞eventbus,也可以是给该组件暴露一个事件。所以两种方式都能用
补充
- call和apply主要差异。第二个参数开始是给函数传参,call为函数的每个参数,apply为参数组成的数组
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates"
}
person.fullName.call(person1, "Seattle", "USA");
------------------------------------------------------------
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);