Vue3 SFC 和 TSX 方式自定义组件实现 v-model
1 v-model
1.1 理解 v-model
v-model 是 vue3 中的一个内置指令,很多表单元素都可以使用这个属性,如 input、checkbox 等,咱可以在自定义组件中实现 v-model。v-model 本质上是一个语法糖:
- 绑定父组件传递过来的 modelValue 属性;
- 值改变时向父组件发出事件 update:modelValue。
1.2 案例描述
理解了 v-model 的本质,咱可以分别使用 SFC(.vue 文件)和 TSX(.tsx)方式定义一个组件 person-name ,使该组件可以使用 v-model。
person-name 包括两个输入框,分别是“姓”(familyName)和“名”(firstName)两个字段,v-model 传递的数据格式为:
{ familyName: '张', firstName: '三' }
首先定义该类型 person-name-type.ts:
export interface PersonName {
/** 姓 */
familyName?: string;
/** 名 */
firstName?: string;
}
1.3 编写样式
编写 person-name.scss 样式文件,后面再两个组件中分别引入:
.person-name {
.el-form-item {
width: 200px;
}
}
2 编写组件
2.1 实现思路
person-name 组件实现逻辑比较简单:
- template 中放置两个输入框 el-input,分别对应 姓 和 名 两个字段;
- 定义两个变量 innerFamilyName 和 innerFirstName 绑定两个输入框的值;
- 在 props 定义 modeValue 属性,接收父组件传递过来的 PersonName 类型的对象;
- 使用 watch 深度监听 modelValue,当其属性值有变化时,重新赋值给上面绑定输入框的两个变量;
- 当两个输入框触发 input 事件时,通过 update:modelValue 事件通知父组件,从而实现 v-model。
2.2 SFC(.vue)中的实现
创建组件文件 person-name-sfc.vue:
2.3 TSX(.tsx)中的实现
创建组件文件 person-name-tsx.tsx:
import { defineComponent, PropType, ref, watch } from 'vue'
import { PersonName } from './person-name-type'
import './person-name.scss'
export default defineComponent({
name: 'person-name-tsx',
props: {
modelValue: {
type: Object as PropType,
required: true,
default: () => ({})
}
},
emits: ['update:modelValue'],
setup (props, context) {
const innerFamilyName = ref(props.modelValue.familyName)
const innerFirstName = ref(props.modelValue.firstName)
const onInput = () => {
context.emit('update:modelValue', {
familyName: innerFamilyName.value,
firstName: innerFirstName.value
})
}
watch(() => props.modelValue, (newVal) => {
innerFamilyName.value = newVal?.familyName || ''
innerFirstName.value = newVal?.firstName || ''
}, {
deep: true,
immediate: true
})
return () => (
)
}
})
3 使用组件
创建父组件 demo-v-model.vue,在里面使用上面定义的两个组件:
reset
{{personName1}}
reset
{{personName2}}
运行效果如下:

上面部分使用 .vue 编写的组件,下面部分使用 .tsx 编写的组件,两者独立绑定 v-model,运行效果完全一致。
- 子组件可以接收到父组件传递的初始值;
- 子组件值改变时会通知到父组件;
- 父组件改变值时,子组件会响应变更。