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

本文详细介绍了如何在Vue3中使用SFC和TSX方式实现自定义组件的v-model功能。通过理解v-model的本质,创建了一个名为`person-name`的组件,包含两个输入框分别对应姓和名。在`.vue`和`.tsx`文件中分别实现组件,并通过watch和input事件实现数据双向绑定。最后展示了在父组件中使用这两个组件的示例,两者效果一致。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>

运行效果如下:

image-20221020114141658

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

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

感谢你阅读本文,如果本文给了你一点点帮助或者启发,还请三连支持一下,点赞、关注、收藏,作者会持续与大家分享更多干货

<template> <!-- 使用 v-model:value 来绑定编辑器的内容 --> <VAceEditor v-model:value="content" :lang="language" :theme="theme" :options="editorOptions" style="height: 300px; width: 100%" /> <!-- 渲染动态组件 --> <component :is="Comp" /> </template> <script setup> import { ref, defineComponent, compile, watch } from "vue"; import { VAceEditor } from "vue3-ace-editor"; // 引入 VAceEditor 组件 import "ace-builds/src-noconflict/mode-vue"; // 引入 Vue 模式支持 import "ace-builds/src-noconflict/theme-github"; // 引入 GitHub 主题样式 // 定义编辑器的内容、语言模式、主题配置选项 const content = ref(`<div>Hello, Vue Editor!</div>`); // 初始化代码内容 const language = ref("vue"); // 设置默认语言为 Vue const theme = ref("github"); // 设置默认主题为 GitHub 风格 // 编辑器的配置选项,如字体大小、是否显示打印边距等 const editorOptions = ref({ fontSize: "14px", // 设置字体大小 showPrintMargin: false, // 是否显示打印边距 enableLiveAutocompletion: true, // 启用实时自动补全功能 enableSnippets: true, // 启用代码段 highlightActiveLine: true, // 高亮行 highlightSelectedWord: true, // 高亮选中的字符 tabSize: 4, // tab缩进字符 }); // 动态编译用户输入的 Vue 代码 const Comp = ref(null); const updateComp = () => { console.log(compile,&#39;compile&#39;) try { const result = compile(content.value); console.log(result, "result"); const { render, staticRenderFns } = result; Comp.value = defineComponent({ render, staticRenderFns, }); } catch (error) { console.error("编译 Vue 代码时出错:", error); } }; // 监听内容变化,更新组件 watch( content, () => { updateComp(); }, { immediate: true } ); </script> <!-- 注释:以上代码展示了如何在 Vue 3 中使用 vue3-ace-editor 让用户输入 Vue 代码, 并通过动态组件渲染功能将用户输入的代码渲染出来。通过 Composition API <script setup> 语法糖,我们可以简洁地引入组件,并利用 Vue 3 的响应式系统来管理状态。 -->修复这段代码
04-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员优雅哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值