Vue3 SFC 和 TSX 方式自定义组件实现 v-model

  • Vue3 SFC 和 TSX 方式自定义组件实现 v-model已关闭评论
  • 105 次浏览
  • A+
所属分类:Web前端
摘要

v-model 是 vue3 中的一个内置指令,很多表单元素都可以使用这个属性,如 input、checkbox 等,咱可以在自定义组件中实现 v-model。v-model 本质上是一个语法糖:


1 v-model

1.1 理解 v-model

v-model 是 vue3 中的一个内置指令,很多表单元素都可以使用这个属性,如 inputcheckbox 等,咱可以在自定义组件中实现 v-modelv-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 组件实现逻辑比较简单:

  1. template 中放置两个输入框 el-input,分别对应 两个字段;
  2. 定义两个变量 innerFamilyNameinnerFirstName 绑定两个输入框的值;
  3. props 定义 modeValue 属性,接收父组件传递过来的 PersonName 类型的对象;
  4. 使用 watch 深度监听 modelValue,当其属性值有变化时,重新赋值给上面绑定输入框的两个变量;
  5. 当两个输入框触发 input 事件时,通过 update:modelValue 事件通知父组件,从而实现 v-model

2.2 SFC(.vue)中的实现

创建组件文件 person-name-sfc.vue

<template>   <div class="person-name">     <el-form-item label="姓">       <el-input v-model="innerFamilyName" @input="onInput"></el-input>     </el-form-item>     <el-form-item label="名">       <el-input v-model="innerFirstName" @input="onInput"></el-input>     </el-form-item>   </div> </template>  <script lang="ts" setup name="person-name-sfc"> import { PropType, ref, watch } from 'vue' import { PersonName } from './person-name-type'  const props = defineProps({   modelValue: {     type: Object as PropType<PersonName>,     required: true,     default: () => ({})   } })  const emits = defineEmits(['update:modelValue'])  const innerFamilyName = ref('') const innerFirstName = ref('')  watch(() => props.modelValue, (newVal) => {   innerFamilyName.value = newVal?.familyName || ''   innerFirstName.value = newVal?.firstName || '' }, {   deep: true,   immediate: true })  const onInput = () => {   emits('update:modelValue', {     familyName: innerFamilyName.value,     firstName: innerFirstName.value   }) } </script>  <style scoped lang="scss"> @import "./person-name"; </style> 

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<PersonName>,       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 () => (       <div class="person-name">         <el-form-item label="姓">           <el-input vModel={innerFamilyName.value} onInput={onInput}/>         </el-form-item>         <el-form-item label="名">           <el-input vModel={innerFirstName.value} onInput={onInput}/>         </el-form-item>       </div>     )   } }) 

3 使用组件

创建父组件 demo-v-model.vue,在里面使用上面定义的两个组件:

<template>   <div>     <person-name-sfc v-model="personName1"></person-name-sfc>     <el-button @click="onResetClick1">reset</el-button>     <div>{{personName1}}</div>   </div>   <el-divider />   <div>     <person-name-tsx v-model="personName2"></person-name-tsx>     <el-button @click="onResetClick2">reset</el-button>     <div>{{personName2}}</div>   </div> </template>  <script lang="ts" setup> import PersonNameSfc from '@/components/model/person-name-sfc.vue' import { ref } from 'vue' import { PersonName } from '@/components/model/person-name-type' import PersonNameTsx from '@/components/model/person-name-tsx'  const defaultPersonName = { familyName: '张', firstName: '三' }  const personName1 = ref<PersonName>({ ...defaultPersonName }) const personName2 = ref<PersonName>({ ...defaultPersonName })  const onResetClick1 = () => {   personName1.value = { ...defaultPersonName } } const onResetClick2 = () => {   personName2.value = { ...defaultPersonName } } </script> 

运行效果如下:

Vue3 SFC 和 TSX 方式自定义组件实现 v-model

上面部分使用 .vue 编写的组件,下面部分使用 .tsx 编写的组件,两者独立绑定 v-model,运行效果完全一致。

  1. 子组件可以接收到父组件传递的初始值;
  2. 子组件值改变时会通知到父组件;
  3. 父组件改变值时,子组件会响应变更。