快应用常用组件开发-图标和表单
承接上一篇构建组件库的文章,这篇将详细讲一讲组件的开发过程,和两个组件开发示例:图标和表单。
自定义组件
快应用的组件化做得是比较精简的,一个组件的实现和一个页面基本一致,自定义组件和页面的区别在于多了一些属性,少了一些生命周期的钩子。
多的属性是你可以向组件内传递数据的参数,可以用下面这种方式:
<template>
<div class="child-demo">
<text>{{ propOne.name }}</text>
</div>
</template>
<script>
export default {
props: ['propOne']
}
</script>
你可以把要传递进组件的属性在props
这个数组中声明,不过要注意命名方式:声明属性要采用驼峰命名,在外部调用组件时要把驼峰转换成分隔线。以上面为例,调用组件时应该写成prop-one
。
调用方式如下所示:
<import name="comp" src="./component.ux"></import>
<template>
<div class="parent-demo">
<comp prop-one="{{obj}}"></comp>
</div>
</template>
<script>
export default {
private: {
obj: {
name: 'child-demo'
}
}
}
</script>
如果你要为传入的属性设置默认值,可以采取下面的方式声明:
<script>
export default {
props: {
prop1: {
default: 'Hello' //默认值
},
prop2Object: {} //不设置默认值
},
onInit() {
console.info(`外部传递的数据:`, this.prop1, this.prop2Object)
}
}
</script>
图标组件开发和实现
了解了怎么写一个自定义组件,现在我们来实现一个图标库的组件,这在我们前端开发中是一个很常用的组件。
第一步:制作字体图标文件
制作字体图标文件有个很方便的在线工具:icomoon,网站的操作简便,容易上手,这里不重复说明。
第二步:准备好图标库组件的代码
我这里有写好的组件代码,大家拷贝过去修改一下字体图标的路径就可以直接使用,不过有几点需要注意的地方,在下面说明:
- 字体图标只能用于
text
标签,其他标签不会显示; - 字体图标通过
@font-face
引入; - 字体图标需要使用html实体来使用,这是因为快应用不支持伪类导致的,所以一定要保存从icomoon上下载下来的字体图标包和网站上图标库项目,不然没法看到图标对应的html实体值;
做好上面的几点,我们的字体图标组件基本就可用了,代码大概长成下面这样:
<template>
<text if="{{type === 'search'}}" class="font-icon" style="font-size: {{size}}px;color: {{color}};"></text>
<text if="{{type === 'home'}}" class="font-icon" style="font-size: {{size}}px;color: {{color}};"></text>
</template>
<style lang="less">
@font-face {
font-family: iconfont;
src: url('iconfonts.ttf');
}
.font-icon {
font-family: iconfont;
text-align: center;
}
</style>
<script>
export default {
props: {
type: {
default: 'home'
},
size: {
default: 14
},
color: {
default: ''
}
},
}
</script>
第三步:优化调用方式
通过上面的方式,在icon变多的时候,会增加很多text标签,组件的代码会变得很多,也不利于维护和阅读,所以得想个办法,把代码优化一下,你可能很自然就会想到,让type变量直接接收传入的图标对应的html实体值不就好了。就像下面这样:
<template>
<text class="font-icon" style="font-size: {{size}}px;color: {{color}};"></text>
</template>
<style lang="less">
@font-face {
font-family: iconfont;
src: url('iconfonts.ttf');
}
.font-icon {
font-family: iconfont;
text-align: center;
}
</style>
<script>
export default {
props: {
type: {
default: ''
},
size: {
default: 14
},
color: {
default: ''
}
},
}
</script>
如果你把上面的代码跑起来,会发现图标根本不会显示,而且就算能显示,在调用的时候你还得去查一遍图标库,看看要用的图标对应的html实体值,代码的可阅读性也会有所降低,所以这时候,还得有一个骚操作,代码如下:
<template>
<text class="font-icon" style="font-size: {{size}}px;color: {{color}};">{{ unescapeFontIconCode(iconMap[type]) }}
</text>
</template>
<style lang="less">
@font-face {
font-family: iconfont;
src: url('iconfonts.ttf');
}
.font-icon {
font-family: iconfont;
text-align: center;
}
</style>
<script>
export default {
data() {
return {
iconMap: {
empty: '',
home: ''
}
}
},
props: {
type: {
default: 'empty'
},
size: {
default: 14
},
color: {
default: ''
}
},
unescapeFontIconCode(iconCode = '') {
return unescape(iconCode.replace(/&#x/g, '%u').replace(/;/g, ''))
}
}
</script>
上面代码中,我们新增了一个方法unescapeFontIconCode
,这个方法返回unescape
后的字符实体值,这样在text中,图标就能正常显示了;同时,我们还新增了一个iconMap
变量,用来保存icon的字符实体值,同时给每个对应的图标取一个可读性更高的别名,方面我们的调用,也增加了在调用时代码的可读性,一眼就能看出是什么图标。
到此,图标组件我们就编写完了,当然你还可以根据自身业务开发的需求,为这个图标组件增加更多定制化的属性或者方法。
表单组件开发和实现
接下来,我们再做一个表单组件的开发,此处以单选框为例。单选框组件一般会多个配合使用,所以我们这个组件实际上可以拆分成两个不同功能的部分来实现:一部分是单个单选框组件,一部分是多个单选框组件同时使用的时候的group
组件。下面一个一个来。
radio组件实现如下:
<template>
<div class="apex-radio apex-radio-{{position}}" onclick="clickHandler">
<div class="apex-radio-checked {{my_checked?'apex-radio-active':''}}"
style="background-color: {{(my_checked||disabled)?my_color:''}}">
<div class="apex-radio-icon {{disabled?'apex-radio-disabled':''}}" show="{{my_checked}}"></div>
</div>
<div class="apex-radio-content">
<text>{{value}}</text>
</div>
</div>
</template>
<style lang="less">
@import "../styles/base.less";
.apex-radio {
padding: 20px;
background-color: #FFFFFF;
height: 86px;
&-checked {
height: @checkbox-size;
width: @checkbox-size;
justify-content: center;
border-radius: @checkbox-size;
border: 2px solid @border-color-base;
margin-right: 10px;
}
&-icon {
width: @checkbox-size / 2;
height: @checkbox-size / 3.6;
border-style: solid;
border-width: 0 0 2px 2px;
transform: rotate(-45deg);
margin-top: @checkbox-size / 3;
border-color: #FFFFFF;
}
&-active {
border-color: transparent;
}
&-disabled {
border-color: @border-color-base;
}
&-right {
flex-direction: row-reverse;
justify-content: space-between;
}
}
</style>
<script>
export default {
data() {
return {
my_checked: this.checked,
my_position: this.position,
my_color: this.color
};
},
props: {
checked: { // 是否选中
default: false
},
disabled: { // 是否禁用
default: false
},
position: { // 提示文本位置
default: 'left' // left right
},
value: { // 单选的提示文本
default: ''
},
color: { // 单选框的颜色
default: '#2d8cf0'
},
group: { // 所属的单选框组的值,用于在多个单选框中只选中一个
default: ''
}
},
onInit() {
this.$watch('disabled', 'handleDisabled'); // 监测并处理传入的disabled值的变化
this.$watch('checked', 'changeChecked'); // 监测并处理传入的checked值的变化
},
changeChecked() { // 处理传入checked值变化后的的方法
this.my_checked = this.checked;
},
changeCurrent(current) {
this.my_checked = current;
},
clickHandler() { // 单选框被点击时的处理方法
if (this.disabled) return;
const item = {current: !this.checked, value: this.value};
const parent = this.$parent().$child(this.group); // 检测是否在一个单选框组中
parent ? parent.emitEvent(item) : this.$emit('change', item);
},
handleDisabled(e) { // 处理传入disabled值变化后的的方法
if (e) {
this.my_color = '#bbbec4'
} else {
this.my_color = this.color
}
}
};
</script>
这个组件的实现需要注意的有一下几点:
- 需要单独在组件内监测某几个传入值的变化;
- 单选框点击事件内需要判断当前的组件是否处于一个单选框组(radio-group)中,这里用到的判断方法主要是这一句代码
const parent = this.$parent().$child(this.group)
。
下面是radio-group组件的实现方法:
<template>
<div id="radio-group" class="apex-radio-group">
<slot></slot>
</div>
</template>
<style lang="less">
@import "../styles/base.less";
.apex-radio-group {
flex-direction: column;
}
</style>
<script>
export default {
data() {
return {};
},
props: {
current: {
default: ''
},
},
onInit() {
this.$watch('current', 'changeCurrent');
},
changeCurrent(val = this.current) {
let items = this.$child('radio-group')._slot.childNodes[0].childNodes;
const len = items.length;
if (len > 0) {
items.forEach(item => {
item._vm.changeCurrent(val === item._vm.value);
});
}
},
emitEvent(params) {
this.$emit('change', params);
}
};
</script>
单选框组的实现相对单选框就简单多了,不过有个需要之一的地方就是,如何获取slot中的元素,从而判断哪一个单选框是当前选中的,同时也保障一个组内有且仅有一个处于选中状态,这里用了一些hack的办法实现,也是官方文档中没有说明的方式。
关键代码是这两句:
let items = this.$child('radio-group')._slot.childNodes[0].childNodes; // 获取当前组内的单选框组件
item._vm.changeCurrent(val === item._vm.value); // 访问组内单选框组件的方法
另外:目前在快应用中只支持一个slot标签。
尾声
以上讲解了快应用自定义组件的开发方法,以及分析了两个常用组件的代码实现,你也可以试着自己实现一遍,看有没有更好的方法,这两个组件都是快应用开源组件库apex-ui中的组件,如果你有更好的方法,欢迎提issue,pr。