前言:最近在做毕设的时候,需要二次封装Naive UI的Input组件,达到多次复用的效果,然后遇到了需要v-model来二次传值的需求,实现方法本身不难,这里把Vue3的做法叙述一下,Vue2应该也差不多。

操作方法

  • 所谓的v-model其实就是v-bind搭配v-onemit的组合语法糖,可以通过这三个东西来定义需要双向绑定的变量名
  • 直接上代码

父组件

1
<Input v-model:value="val" />

子组件

  • 子组件需要的做的事情其实不多,通过props定义一个叫value的变量,然后绑定到input属性的:value上面
  • props中的value你可以给他定义类型,也可以不定义,看你喜欢
  • 然后需要定义一个update:value的emit事件,一定要叫这个名字一定要叫这个名字——update:value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<input :value="props.value" @input="Inputing" />
</template>

<script lang="ts" setup>
import { ref, toRefs, computed } from 'vue';
const props = defineProps({
value: {
type: [String, Number, Boolean, Array, Object],
default: ''
}
})
const emit = defineEmits(["update:value"])

function Inputing(el: any) {
const value = el.target ? el.target.value : el
emit('update:value', value)
}

</script>
  • 如果你的props中定义的不是value,比如是title,那么你的父组件应该是这样的👇
1
<Input v-model:title="val" />
  • 子组件则是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<input :value="props.title" @input="Inputing" />
</template>

<script lang="ts" setup>
import { ref, toRefs, computed } from 'vue';
const props = defineProps({
title: {
type: [String, Number, Boolean, Array, Object],
default: ''
}
})
const emit = defineEmits(["update:title"])

function Inputing(el: any) {
const value = el.target ? el.target.value : el
emit('update:value', value)
}

</script>

emit的事件名一定是update: + 你要用v-model双向绑定传值的变量名,也就是上面props中的value或者是title

核心方法

1
2
3
4
function Inputing(el: any) {
const value = el.target ? el.target.value : el
emit('update:value', value)
}
  • v-on:input绑定的方法,我不知道是Naive UI中的@input方法是不是和原生的不太一样
  • 原生的似乎在输入的时候返回了$event事件,但是Naive UI中的input组件第一次是返回$event事件,第二次后面开始返回输入的值
  • 所以这里我做了一个判断,如果el是$event事件,那么它会有个target,然后取$event.target.value即可
  • 如果没有则直接取它的值,不然会有**[object InputEvent]**的BUG

[object InputEvent]的BUG

解决方案就是这个

1
const value = el.target ? el.target.value : el

如果是和我一样用了Navie UI中的Input进行一个v-model的二次封装的话,可以用 :on-update:value来进行emit方法的绑定

  • 代码
1
<n-input :value="props.value" :on-update:value="Inputing"/>

其他方法

  • 除了上面说的这种通过原生的input事件搭配v-on的方法实现父子组件的v-model传值的方法外,还有其他可以用的方法

  • 比如可以通过watch方法进行监听props.value,当value发生变化的时候,通过emit给父组件传改变后的值

  • 也可以通过计算属性computed来拦截父组件传过来的value,进行修改。

  • 另外,这个v-model的父子传值还能绑定多个,也就是说可以搞多个v-model的绑定,进行父子传值间的双向绑定。

最后附上自己的Naive UI的Input组件二次封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<template>
<div class="flex items-center inputString" v-if="!selfSelect && (typeof (props.value) !== 'number')">
<span>{{ title }}</span>
<n-input :placeholder="placeholder" :value="props.value || ''" class="w-50 m-2" :on-update:value="Inputing"
:disabled="props.disabled" :maxlength="selfMaxLength" :allow-input="selfAllow" ref="inputInstRef">
<template #prefix v-if="props.Icon">
<n-icon :component="Icon" />
</template>
</n-input>
</div>
<!-- 文字输入 -->
<div class="flex items-center inputNumber" v-else-if="!selfSelect && typeof (props.value) === 'number'">
<span>{{ title }}</span>
<n-input-number :placeholder="placeholder" :value="props.value" class="flex items-center popInput w-full"
:on-update:value="Inputing" :disabled="props.disabled" :show-button="false" :min="1" :validator="validator"
ref="inputInstRef">
<template #prefix v-if="props.Icon">
<n-icon :component="Icon" />
</template>
</n-input-number>
</div>
<!-- 选项 -->
<n-space vertical v-else-if="selfSelect">
<div class="flex items-center">
<span>{{ title }}</span>
<n-select :value="props.value || ''" :options="Options" @update:value="Inputing" />
</div>
</n-space>
</template>

<script lang="ts" setup>
import vail from '@/utils/validator';
import { ref, toRefs, computed } from 'vue';
import { useState } from '@/stores';
const props = defineProps({
Icon: {
default: ''
},
value: {
type: [String, Number, Boolean, Array, Object],
default: ''
},
title: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(["update:value"])

function Inputing(el: any) {
// console.log('Inputing', el)
// const value = el.target ? el.target.value : el
emit('update:value', el)
}

const vailRule = vail();

const index = Object.keys(vailRule).indexOf(props.title)
const selfRequired = ref(false)
const selfMaxLength = ref()
const selfMessage = ref(false)
const selfAllow = ref()
const selfPattern = ref()
const selfSelect = ref(false)
if (index !== -1) {
const { required, maxlength, message, allowInput, pattern, select } = vailRule[props.title]
selfRequired.value = required
selfMaxLength.value = maxlength
selfMessage.value = message
selfAllow.value = allowInput
selfPattern.value = pattern
if (select) {
selfSelect.value = select
}
}

const validator = (x: number) => x >= 1

// 角色选项
const { Options } = toRefs(useState())

</script>

<style lang="scss" scoped>
span {
text-align: center;
margin: 0 10px;
width: fit-content;
display: -webkit-box;
-webkit-line-clamp: 1;
/* 显示的行数,这里限制为两行 */
-webkit-box-orient: vertical;
/* 超出盒子部分隐藏 */
text-overflow: ellipsis;
/* 文字超出两行的部分为省略号 */
white-space: nowrap;
}
</style>