3

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

 1 year ago
source link: https://www.fly63.com/article/detial/12207
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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. 定义两个变量 innerFamilyName 和 innerFirstName 绑定两个输入框的值;
  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>

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

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

链接: https://www.fly63.com/article/detial/12207


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK