前言

使用 uni-app 开发的时候,经常会使用到 picker 选择器,官方自带的 picker组件必须通过change事件进行赋值,如果支持 v-model 的话,开发起来会更加便利。

这篇文章便对 uni-app 中的picker组件进行简单二次封装,使其支持 v-model 指令。

一、核心思想

Vue中的v-model实际上是一个语法糖:

<input v-model="key" />
<!-- 等同于 -->
<input :value="key" @input="key=$event.target.value" />

二、封装组件

1.初步封装

我们希望在父组件中使用以下方式调用,且不需要通过change事件,实现 currentValue 的值跟随picker组件的选择而改变:

<!-- 父组件  -->
<myPicker :options="options" v-model="currentValue"></myPicker>

options = ["广东","上海","北京","深圳"]

在子组件中,我们仅需要在原有picker组件的条件下,接收 value 参数并监听 input 方法,就可以实现双向绑定的效果了:

<!-- 子组件 -->
<template>
	<picker @change="handleChange" :value="index" :range="options">
		{{options[index] || placeholder}}
	</picker>
</template>

<script>
export default {
	name: "myPicker",
	props: {
		value: String | Number,
		options: Array,
		placeholder: {
			type: String,
			default: "请选择"
		}
	},
	data() {
		return {
			index: -1
		};
	},
	watch:{
		// 解决异步加载带来的回显问题
		value(val){
			this.index = this.options.findIndex(item => item == val);
		}
	},
	methods: {
		handleChange(e) {
			this.index = e.target.value;
			let currentValue = this.options[this.index];
			this.$emit("input", currentValue)
			// 用户可以通过监听 @change 事件,在选中的时候进行额外操作
			this.$emit("change", currentValue)
		},
	}
}
</script>

效果图:
在这里插入图片描述

2.完善组件

在实际开发中,picker组件的options数据大多是数组对象的形式,纯数组的情况会相对少一些(例如:选中值返回id),我们刚刚封装的组件虽然支持 v-model ,但并不支持这种较为复杂的数据格式,这个时候我们就需要对它进行完善,让这个组件支持两种options格式的数据。

我们将上述例子的options改成以下形式:

options: [
	{
		value: "gd",
		label: "广东"
	},
	{
		value: "sh",
		label: "上海"
	},
	{
		value: "bj",
		label: "北京"
	},
	{
		value: "sz",
		label: "深圳"
	}
]

用户传递该数组的时候,picker选择器展示 label 的值,选中值为对应的value。

在子组件中,我们必须多接收两个参数,一个是数据展示的文本,一个是返回数据的字段:

props: {
    // 传label则说明组件会将数组对象中label的文本展示出来
	rangeKey: String,
	// 默认情况下,会将label对应的value字段返回出去,可以根据自己的数据格式进行传递
	rangeValue: {
		type: String,
		default: "value"
	}
}

子组件中,options为纯数组的情况下,我们可以通过 options[index] 来展示picker组件所选中的值,而数组对象则不能这么做,我们可以通过一个计算属性来展示当前选中的值:

computed: {
	currentValue() {
		if (this.rangeKey) {
			// index为-1的时候会报错,需要对其进行特殊处理
			return this.index == -1 ? "" : this.options[this.index][this.rangeKey]
		} else {
			return this.options[this.index]
		}
	}
}

两种数据格式下,改变返回的值也不同,因此子组件中的change事件和watch事件必须做对应处理:

// watch事件
watch: {
	value(val) {
		this.index = this.rangeKey ? this.options.findIndex(item => item[this.rangeValue] == val) : this.options.findIndex(item => item == val);
	}
},
// chang事件
handleChange(e) {
	this.index = e.target.value;
	let currentValue = this.rangeKey ? this.options[this.index][this.rangeValue] : this.options[this.index];
	this.$emit("input", currentValue)
	this.$emit("change", currentValue)
}

效果图:
在这里插入图片描述
到这里就完成了一个带 v-model 指令的picker组件了,官方的picker组件有许多中mode,这里仅封装selector(单列选择器),有需要的话按照这个思路也可以将所有的模式封装进去,不过更加推荐多种模式分开封装,以便后期的维护。

三、组件使用

父组件:

<!-- 纯数组形式 -->
<myPicker :options="options" v-model="currentValue"></myPicker>
<!-- 数组对象形式 -->
<myPicker :options="options" v-model="currentValue" rangeKey="label"></myPicker>

子组件代码:

<template>
	<picker @change="handleChange" :value="index" :range="options" :range-key="rangeKey">
		{{currentValue || placeholoder}}
	</picker>
</template>

<script>
	export default {
		name: "myPicker",
		props: {
			value: String | Number,
			options: Array,
			rangeKey: String,
			rangeValue: {
				type: String,
				default: "value"
			},
			placeholoder: {
				type: String,
				default: "请选择"
			}
		},
		data() {
			return {
				index: -1
			};
		},
		computed: {
			currentValue() {
				if (this.rangeKey) {
					return this.index == -1 ? "" : this.options[this.index][this.rangeKey]
				} else {
					return this.options[this.index]
				}
			}
		},
		watch: {
			value(val) {
				this.index = this.rangeKey ? this.options.findIndex(item => item[this.rangeValue] == val) : this.options.findIndex(item => item == val);
			}
		},
		methods: {
			handleChange(e) {
				this.index = e.target.value;
				let currentValue = this.rangeKey ? this.options[this.index][this.rangeValue] : this.options[this.index];
				this.$emit("input", currentValue)
				this.$emit("change", currentValue)
			},
		}
	}
</script>

组件内部并没有编写样式,大家可以根据自己需求进行编写!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐