-
Notifications
You must be signed in to change notification settings - Fork 189
65.从 Vue.js 自定义输入框深入理解 v-model #70
Description
Vue.js 中使用内置的 v-model 指令通过绑定值和捕获 input 事件来模拟双向绑定。
在官方文档中也只是对 input 输入框做了自定义的组件,并没有 radio 和 checkbox 的举例。
关于 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">
如果获取的值不希望是 true 或 false 还可以加上 true-value 和 false-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-value 和 false-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 官方以提供了很多优秀的第三方组件库,自定义组件的实现原理其实也大同小异。