Skip to content

Navigation Menu

Sign in
Sign up

65.从 Vue.js 自定义输入框深入理解 v-model #70

Open
Assignees
Labels

Description

Vue.js 中使用内置的 v-model 指令通过绑定值和捕获 input 事件来模拟双向绑定。

官方文档中也只是对 input 输入框做了自定义的组件,并没有 radiocheckbox 的举例。

关于 v-model

表单的处理在官方文档已经说的很细了,这里再深入一番。

input 输入框

input输入框上的 v-model 只是一个简化的指令,它的双向绑定原理很简单,如下:

<input v-model="msg" placeholder="input message">
<p>Msg: {{ msg }}</p>
<p>Msg:</p>
<p>{{ msg }}</p>
<textarea v-model="msg" placeholder="input message"></textarea>

input 或者 textarea 标签上使用 v-model="msg" 相当于

<input :value="msg" @input="e => msg = e.target.value">
<textarea :value="msg" @input="e => msg = e.target.value"></textarea>

radio 单选按钮

正常用法:

<input type="radio" value="msg1" v-model="msg">
<input type="radio" value="msg2" v-model="msg">
<span>data: {{ msg }}</span>

相当于

<input type="radio" value="msg1" :checked="msg == 'msg1'" @change="e => msg = e.target.value">
<input type="radio" value="msg2" :checked="msg == 'msg2'" @change="e => msg = e.target.value">
<span>data: {{ msg }}</span>

checkbox 多选按钮

checkbox 略微复杂,因为涉及到了只有一个还是多个多选框的情况。

如果只有一个 checkbox v-model 会把它视为一个 Boolean 类型的值并且忽略 value, 比如:

<input type="checkbox" value="check" v-model="isChecked">

等价于

<input type="checkbox" value="check" :checked="!!isChecked" @change="e => isChecked = e.target.checked">

如果获取的值不希望是 truefalse 还可以加上 true-valuefalse-value 属性

<input type="checkbox" value="check" v-model="isChecked" true-value="1" false-value="0">

相当于

<input type="checkbox" value="check" :checked="isChecked == '1'" @change="e => isChecked = e.target.checked ? '1' : '0'">

上面的例子只是存在一个多选框的情况,如果多个 checkbox 共用同一个 model,那这些 checkbox 将会把所有选中的值组成一个数组放进去。同时 true-valuefalse-value 属性将不会再有效。

正常写法:

<template>
 <div>
 <input type="checkbox" value="c1" v-model="vals">
 <input type="checkbox" value="c2" v-model="vals">
 <input type="checkbox" value="c3" v-model="vals">
 </div>
</template>
<script>
 export default {
 data: () => ({
 vals: ['c2']
 })
 }
</script>

相当于

<template>
 <input type="checkbox" value="c1" :checked="checkVal('c1')" @change="update">
 <input type="checkbox" value="c2" :checked="checkVal('c2')" @change="update">
 <input type="checkbox" value="c3" :checked="checkVal('c3')" @change="update">
</template>
<script>
 export default {
 data() {
 return { vals: ['c2'] }
 },
 methods: {
 checkVal(val) {
 return this.vals.includes(val)
 },
 update(e) {
 const isChecked = e.target.checked
 const val = e.target.value
 if (isChecked) {
 this.vals.push(val)
 } else {
 this.vals.splice(this.vals.indexOf(val), 1)
 }
 }
 }
 }
</script>

这里得逻辑就相对于复杂了,checkVal 用来判断是否被选中 update 方法用来更新整个被选中值的数组。

v-model 在自定义组件中的使用

自定义组件中也可以使用 v-model

<custom-component v-model="custom" />

和这种用法是一样的:

<custom-component :value="custom" @input="val => custom = val" />

在 2.2.0+ 的版本中可以使用 model 属性在自定义组件中来实现属性和事件的自定义:

export default {
 name: 'custom-component',
 model: {
 prop: 'customVal',
 event: 'customEvent'
 }
}

v-model 将会查询所有的属性来替代 value 属性,并使用 prop 中来替代 input 事件的监听。
因此上面的那个 custom-component 组件被改写为

<custom-component :customVal="custom" @customEvent="val => custom = val" />

自定义 radio 按钮中使用 v-model

label 标签来做模拟一个简单的实现:

<template>
 <label>
 <input type="radio" :checked="checkVal" :value="value" @change="update">
 {{ label }}
 </label>
</template>
<script>
export default {
 model: {
 prop: 'modelVal',
 event: 'change'
 },
 props: {
 value: {
 type: String,
 },
 modelVal: {
 default: ""
 },
 label: {
 type: String,
 required: true
 },
 },
 computed: {
 checkVal() {
 return this.modelVal == this.value
 }
 },
 methods: {
 update() {
 this.$emit('change', this.value)
 }
 }
}
</script>

这里只是做模拟所以 props 中只写了这里能用到的属性

自定义 checkbox 按钮中使用 v-model

因为要支持单个 true false 类型的 checkbox(同时支持 true-value false-value)和多个 checkbox,将所有选中的值存入数组中。因此这里的代码就稍微复杂了一些。

其实只要把上面 checkbox v-model 代码的实现再增加些判断逻辑就能实现:

<template>
 <label>
 <input type="checkbox" :checked="checkVal" :value="value" @change="update">
 {{ label }}
 </label>
</template>
<script>
export default {
 model: {
 prop: 'modelVal',
 event: 'change'
 },
 props: {
 value: {
 type: String,
 },
 modelVal: {
 default: false
 },
 label: {
 type: String,
 required: true
 },
 // 定义 true-value false-value
 trueValue: {
 default: true
 },
 falseValue: {
 default: false
 }
 },
 computed: {
 checkVal() {
 // 判断是一个还是多个 checkbox
 if (this.modelVal instanceof Array) {
 return this.modelVal.includes(this.value)
 }
 return this.modelVal === this.trueValue
 }
 },
 methods: {
 update(event) {
 const isChecked = event.target.checked
 // 这里也要判断是一个还是多个 checkbox
 if (this.modelVal instanceof Array) {
 const newVal = [...this.modelVal]
 if (isChecked) {
 newVal.push(this.value)
 } else {
 newVal.splice(newVal.indexOf(this.value), 1)
 }
 this.$emit('change', newVal)
 } else {
 this.$emit('change', isChecked ? this.trueValue : this.falseValue)
 }
 }
 }
}
</script>

上面自定义的组件代码也不是很复杂,只是为了通过代码解释下 v-model 在内部是如何工作的,所以功能肯定不完整。

最后

vue.js 官方以提供了很多优秀的第三方组件库,自定义组件的实现原理其实也大同小异。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Projects

No projects

Milestone

No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    AltStyle によって変換されたページ (->オリジナル) /