一、基础组件开发
参考官方文档:配合v-model指令使用
1、方式一
原生元素上的用法(表单输入绑定)
如vue文档介绍,如下v-model指令等价于手动连接值绑定和更改事件监听:
<input v-model="text">
<input :value="text" @input="event => text = event.target.value"/>
<input :value="text" @input="text = $event.target.value"/>
使用在组件上,v-model 会被展开为如下的形式:
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
组件内实现:
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
2、方式二
另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的计算属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
3、自定义组件样例(方式一)
本文例子是基于
vue3的,体验组件开发的简单样例。
使用GInputNumber组件
<GInputNumber v-model="num" :min="0" :max="100" @change="handleChange" />
GInputNumber.vue
<template>
<div class="g-input-number" ref="gInputNumber" @mouseover="addHoverClass" @mouseout="removeHoverClass">
<input ref="input" class="g-input-number__inner" type="text" :value="modelValue" @input="handleInput" />
<div class="g-input-number__ft">
<span @click="add">+</span>
<span @click="minus">-</span>
</div>
</div>
</template>
<script>
export default ({
name: 'GInputNumber',
props: {
modelValue: String,
min: Number,
max: Number
},
watch: {
modelValue(newVal) {
console.log('watch modelValue', newVal)
}
},
emits: ["update:modelValue"],
methods: {
setNativeInputValue(val) {
this.$refs.input.value = val;
},
handleInput(event) {
console.log('handleInput', event.target.value);
let newVal = event.target.value;
// 校验,如果不符合则恢复原值
if (new Number(newVal).toString() == 'NaN') {
newVal = this.modelValue;
} //new Number将带有字符的数字转换为只有数字的类型
if (isNaN(newVal) || newVal == '') newVal = 0;
if (this.min != undefined && newVal < this.min) newVal = this.min;
if (this.max != undefined && newVal > this.max) newVal = this.max;
console.log('newVal', newVal);
if (newVal == new Number(this.modelValue)) { //新值和旧值一样时,不会触发modelValue的watch,所以组件显示没有更新,需要单独处理
this.setNativeInputValue(
newVal); // !!! 一开始不太理解为什么没加这一句,有时回显的还是原来的数值,比如100的时候加多一位数1003时newVal是100,但组件显示还是1003
}
this.$emit("update:modelValue", new Number(newVal) + '');
},
addHoverClass() {
this.$refs.gInputNumber.classList.add('hover');
},
removeHoverClass() {
this.$refs.gInputNumber.classList.remove('hover');
},
add() {
let newVal = new Number(this.modelValue) + 1;
if (this.min != undefined && newVal < this.min) newVal = this.min;
if (this.max != undefined && newVal > this.max) newVal = this.max;
this.$emit("update:modelValue", new Number(newVal) + '');
},
minus() {
let newVal = new Number(this.modelValue) - 1;
if (this.min != undefined && newVal < this.min) newVal = this.min;
if (this.max != undefined && newVal > this.max) newVal = this.max;
this.$emit("update:modelValue", new Number(newVal) + '');
}
},
});
</script>
<style scoped lang="less">
.g-input-number {
position: relative;
display: inline-block;
width: 100px;
.g-input-number__inner {
width: 100px;
line-height: 30px;
padding: 1px 30px 1px 30px;
box-sizing: border-box;
text-align: center;
}
.g-input-number__ft {
display: none;
position: absolute;
right: 1px;
top: 2px;
width: 24px;
cursor: pointer;
}
&.hover {
.g-input-number__ft {
display: initial;
span {
display: block;
line-height: 16px;
border-left: 1px solid #ccc;
&:first-child {
border-bottom: 1px solid #ccc;
}
}
}
}
}
</style>
二、封装第三方组件
参考官网文档 透传 Attributes
知识点
attributes透传:
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。
如果不希望自动继承,想指定元素,则可设定inheritAttrs: false,把v-bind="$attrs"加到指定元素上
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
</div>
- 由于
prop是readonly的,组件内需要用计算属性num将modelValue中转一下;(同上“方式二”中所述)
二次封装组件样例
下面基于element-ui的el-input-number,封装的带单位(%)格式的GPercentInputNumber组件
此组件只是做二次封装组件的例子,细节有待完善的。 该组件用到了el-input-number、el-input,el-input只是做显示格式化作用。
使用GPercentInputNumber组件
<GPercentInputNumber v-model="numP" :min="0" :max="100" @change="handleChange2" />
GPercentInputNumber.vue
<template>
<div class="g-percent-input-number">
<el-input-number ref="button" v-bind="filteredAttrs" v-model="num" />
<el-input class="label-input" ref="inputText" v-model="text" @input="onInputText" :formatter="formatter"
:parser="parser" />
</div>
</template>
<script>
export default {
name: 'GInputNumber',
inheritAttrs: false,
props: {
modelValue: String,
addonAfter: {
type: String,
default: '%'
}
},
data() {
return {}
},
watch: {},
computed: {
filteredAttrs: function () {
return {
...this.$attrs,
// modelValue: undefined,
};
},
num: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
},
text: {
get() {
return this.formatter(this.modelValue)
},
set(value) {
console.log('text set ', value)
this.$emit('update:modelValue', this.parser(value))
}
}
},
mounted() {
console.log('attrs', this.$attrs);
},
methods: {
onInputText(value) {
console.log('onInputText', value)
},
formatter(value) {
console.log('formatter before', value);
let text = '';
if (new Number(value).toString() == 'NaN') {
text = this.modelValue + `${this.addonAfter}`; //遗留问题未处理,如果输入了字母如2a%
} else text = `${value}${this.addonAfter}`;
console.log('formatter after', text);
return text;
},
parser(value) {
console.log('parser', value);
try {
return value.replace(this.addonAfter, '');
} catch (e) {
return 0;
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.g-percent-input-number {
position: relative;
.label-input {
position: absolute;
display: inline-block;
width: 66px;
margin-left: -107px;
overflow: hidden;
line-height: 28px;
height: 30px;
margin-top: 1px;
::v-deep .el-input__wrapper {
border: none;
box-shadow: none;
.el-input__inner {
text-align: center;
}
}
}
}
</style>