Vue v-model 实现
1. 基本原理
1.1 表单元素
v-model
指令在表单元素(<input>
、<textarea>
及 <select>
)上创建的双向数据绑定。会根据控件的类型自动选取正确的方法来更新元素值。
1.2 自定义组件
在自定义组件上,v-model
本质是语法糖,会将值绑定到默认的 prop(vue2:value
) 上,监听组件内部抛出的默认事件(vue2: input
)更新元素值。
v-model
prop 和 事件 的默认名称:
Vue2 | Vue3 | |
---|---|---|
prop | value | modelValue |
事件 | input | update:modelValue |
在自定义组件上,
原理是用 v-bind 绑定value值,用 v-on 监听值的变化并重新赋值,以 Vue2 自定义组件 <my-input>
为例,组件外部监听input事件变化代码如下:
<my-input v-model="myValue">
<!-- 是以下的简写: -->
<!-- 1. 组件上 -->
<my-input v-bind:value="myValue" v-on:input="(val) => { myValue = value}">
<!-- 2. 组件内部 -->
<template>
<input
v-bind="$attrs"
v-bind:value="myValue"
v-on:input="$emit('input', $event.target.value)"
>
</template>
2. 自定义组件,prop 和 event 的默认值修改
2.1 Vue2
// 选项式 API
model: {
prop: 'checked', // 默认为 value
event: 'change' // 默认为 input
},
props: {
checked: Boolean
},
2.2 Vue3
<ChildComponent v-model:title="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
3. .sync
修饰符(Vue3中已移除)
为 prop 提供从内部调用 this.$emit('update:title', newTitle) 修改能力的语法糖
prop是默认是不进行双向绑定的,因为双向绑定会带来维护上的问题(试想一下,子组件可以变更父组件,且在父组件和子组件两侧都没有明显的变更来源,都是直接赋值修改。那么父组件中的这个值会变的十分难以预判)。所以 Vue 推荐以 update:myPropName
形式触发事件,来实现 prop 的双向绑定,例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="(newTitle) => doc.title = newTitle"
></text-document>
// text-document 组件内部,通过触发 `update:title` 方法修改外部的值
this.$emit('update:title', newTitle)
为了方便起见,我们为这种模式提供一个缩写(语法糖),即 .sync
修饰符:
<text-document v-bind:title.sync="doc.title" ></text-document>
PS: Vue3 中可通过 v-model
添加参数实现,上例在Vue3中同下
<text-document v-model:title="doc.title" ></text-document>
4. Vue3 中的 defineModel() 和 useModel()
4.1 defineModel()
仅在 3.4+ 版本中可用
这个宏可以用来声明一个双向绑定 prop,通过父组件的v-model
来使用。
文档:https://cn.vuejs.org/api/sfc-script-setup.html#definemodel
// 声明 "modelValue" prop,由父组件通过 v-model 使用
const model = defineModel()
// 或者:声明带选项的 "modelValue" prop
const model = defineModel({ type: String })
// 在被修改时,触发 "update:modelValue" 事件
model.value = "hello"
// 声明 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel("count")
// 或者:声明带选项的 "count" prop
const count = defineModel("count", { type: Number, default: 0 })
function inc() {
// 在被修改时,触发 "update:count" 事件
count.value++
}
4.2 useModel()
仅在 3.4+ 版本中可用
这是驱动defineModel()
的底层辅助函数。如果使用<script setup>
,应当优先使用defineModel()
。
示例:
export default {
props: ['count'],
emits: ['update:count'],
setup(props) {
const msg = useModel(props, 'count')
msg.value = 1
}
}
5. JSX / TSX 中的写法
render: scope => {
return (
<InputInteger model-value={scope.row.tchNum} onUpdate:modelValue={val => (scope.row.tchNum = val)} />
);
}