为了使组件使用起来更简单或者简洁,我们在vue项目中可能需要自定义组件,并且为它实现v-model,以下介绍具体的实现(此处是对element-ui组件el-select的二次封装)
v-model实现原理: v-model其实是一个语法糖,绑定了一个叫value的属性,然后处理了一个叫input的事件,事件中把返回值重新赋给了value,如下:
<input v-model="val"/>
<!--等价于-->
<input :value="val" @input="val = $event.target.value"/>
当然,value和input的名字已经支持自定义了,在组件中添加model的属性去设置:
model: {//model选项可以自定义prop名称和event名称
prop: 'valueeee',
event: 'inputtt'
},
data(){return{}},
methods:{},
//...
所以我们的自定义组件需要接收一个叫value的属性,并对外暴露一个叫input的事件去更新绑定值。
1.组件template部分:
<template>
<el-select
filterable
placeholder="请选择"
v-model="innerValue"
@change="changeHandler"
:multiple="multiple"
:collapse-tags="true">
<el-option
v-for="item in optionList"
:key="item.key"
:label="item.value"
:value="item.key">
</el-option>
</el-select>
</template>
2.组件js部分:
<script>
export default {
name: "vvSelect",
data(){
return{
optionList: [], //下拉列表数据
optionMap: {}, //下拉列表映射集,往往对组件的使用者很有用
innerValue: null //v-model实现 step3.子组件中不允许直接修改prop数据
}
},
props: {
url: {//请求地址
type: String,
//这个默认值可以是通用的数据字典的api,在使用数据字典作为下拉时可减少配置
default: "/dict/getListByTypeCode"
},
params:{//查询参数(对象or数组),配合url使用
type:[Object, Array, String], //string时为数据字典的类型标识(typeCode)
default:function(){
return {}; //默认空对象
}
},
options: { //直接指定下拉列表(optionList)的数据, 优先级高于url
type: [Object, Array], //为Object时需指定path
default:function(){
return null;
}
},
value: [String,Number], //v-model实现,step1.需要绑定value
keyName: { //选项value属性名
type: String,
//这个默认值可以是数据字典的code属性名称,使用数据字典作为下拉时可减少配置
default: "codeName"
},
valueName: { //选项label属性名
type: String,
//这个默认值可以是数据字典的value属性名称,使用数据字典作为下拉时可减少配置
default: "codeValue"
},
path:{ //xx.yy的格式指定从返回结果解析数组的路径
type: String,
default: ''
},
refresh: { //该值变化会引起 选项刷新 建议:refresh = !refresh
type:Boolean,
default: true //默认第一次自动加载,在父组件中指定false则第一次不会加载
},
multiple:{ //是否多选,默认发生false
type: Boolean,
default: false
},
labelGroup:{ //下拉项是否展示成 value[key] 的形式
type: Boolean,
default: false
}
},
watch:{
refresh(){ //更新标识观测
this.loadOptionList();
},
value(val){ //将value同步到innerValue
this.innerValue = val;
}
},
methods:{
//change事件处理
changeHandler(val){
this.$emit('input',val) //v-model实现 step2.需要触发input
this.$emit('change',val) //将原change事件暴露
},
//加载下拉选项列表
//加载下拉列表有两种方式: 通过指定是数据options或者url远程请求
//options的优先级高于url的方式
loadOptionList(){
if(this.options){
this.setOptionList(this.options);
}else{
let reqParams = this.params;
if(typeof this.params === "string"){
reqParams = {typeCode : this.params};//数据字典typeCode设置
}
//$postApi为项目中自定义的全局ajax请求Promise函数
this.$postApi(this.url, reqParams).then(resp => {
const {result, msg, data } = resp;
if(result){
this.setOptionList(data);
}else{
this.$message.error(msg);
}
});
}
}
setOptionList(data){
let list = data;
if(this.path){//通过path寻找数据列表
const pathArr = this.path.split('.');
for(let subPath of pathArr){
list = list[subPath];
}
}
this.optionList = [];
if(list && list.length){
for(let item of list){
const key = item[this.keyName];
let value = item[this.valueName];
value = this.labelGroup ? (value+"["+key+"]") : value;
this.optionList.push({key, value });
this.optionMap[key] = item;
}
}
this.$emit('getOptions',this.optionMap,this.optionList); //父组件可获得相关数据
}
},
mounted(){
if(this.refresh){//第一次加载, refresh=false则第一次加载不会执行
this.loadOptionList();
}
}
}
</script>
3.使用(使用前记得注册组件,全局或者局部注册都可以):
1.可传属性:
url:string 下拉列表请求地址
params:object/array 请求参数
options:object/array 下拉列表数据
path:string 提取下拉选项数组的路径,根路径为 data 格式: xxx.yyy.zzz
keyName:string 字典码属性名
valueName:string 字典值属性名
refresh:boolean 刷新标识 建议:refresh = !refresh 来刷新列表
multiple:boolean 是否多选,默认false
labelGroup:boolean 下拉项的值是否展示成 value[key] 的形式 默认false
2.v-model: 可绑定一个值
3.可用事件:
@change 等价于原组件的change事件
@getOptions(arg1,arg2) 获取下拉选项的以key为键的对象(map)以及数组
arg1:object 下拉选项的map
arg2:array 下拉选项数组(值包含key和value属性)
主要适用于常见的3种场景,以下举例:
<!--数据源为数据字典的下拉框 如性别 返回数据data为: []-->
<vv-select v-model="user.sex" params="SEX_TYPE" labelGroup></vv-select>
<!--数据源为远程非数据字典的下拉框 如某分类, 返回数据data为: {classify: { list: [] } }-->
<!--优先级高于上一个(即未指定url的情况)-->
<vv-select
v-model="user.classify"
url="aaa/listClassifyByType"
:params="{type: 1}"
keyName="classifyId"
valueName="classifyName"
path="classify.list">
</vv-select>
<!--指定options的场景(optionsData为数组),优先级高于前面的两个-->
<vv-select
v-model="user.xxx"
:options="optionsData"
keyName="code"
valueName="value">
</vv-select>
<!--在给定的options或者url请求返回的数据不是下拉数组的数据的时候,需要指定path来找到下拉列表的数据-->