当前位置: 首页 > news >正文

Vue3.0: v-model 组件双向绑定学习文档 (v3.4 前后对比 + TypeScript)

Vue3.0: v-model 组件双向绑定学习文档 (v3.4 前后对比 + TypeScript)

文章目录

  • Vue3.0: v-model 组件双向绑定学习文档 (v3.4 前后对比 + TypeScript)
    • 1. v-model 基础概念
      • 1.1 什么是 v-model
    • 2. Vue 3.4 之前的 v-model 用法 (TypeScript)
      • 2.1 基本用法
      • 2.2 多个 v-model 绑定
      • 2.3 带类型的模型
      • 2.4 修饰符处理
    • 3. Vue 3.4 之后的新 v-model 用法 (TypeScript)
      • 3.1 `defineModel()` 宏的引入
      • 3.2 新旧写法对比
      • 3.3 详细用法对比
        • 3.3.1 基本输入框组件
        • 3.3.2 带类型的模型
      • 3.4 多个 v-model 绑定
      • 3.5 修饰符处理
      • 3.6 高级用法
        • 3.6.1 带默认值和验证
        • 3.6.2 自定义 getter/setter
        • 3.6.3 复杂组件示例
    • 4. 实际项目示例
      • 4.1 搜索组件迁移
      • 4.2 表单选择器组件
    • 5. 类型安全和最佳实践
      • 5.1 严格的类型定义
      • 5.2 异步操作处理
    • 6. 总结
      • 主要优势:
      • TypeScript 最佳实践:

1. v-model 基础概念

1.1 什么是 v-model

v-model 是 Vue.js 中用于实现表单输入元素双向数据绑定的指令,是 :modelValue@update:modelValue 的语法糖。

2. Vue 3.4 之前的 v-model 用法 (TypeScript)

2.1 基本用法

子组件实现:

<template><input:value="modelValue"@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)">
</template><script setup lang="ts">
interface Props {modelValue: string
}interface Emits {(e: 'update:modelValue', value: string): void
}const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

父组件使用:

<template><div><CustomInput v-model="inputValue" /><p>输入的值: {{ inputValue }}</p></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'const inputValue = ref<string>('')
</script>

2.2 多个 v-model 绑定

子组件:

<template><div class="user-form"><input:value="firstName"@input="$emit('update:firstName', ($event.target as HTMLInputElement).value)"placeholder="名"><input:value="lastName"@input="$emit('update:lastName', ($event.target as HTMLInputElement).value)"placeholder="姓"></div>
</template><script setup lang="ts">
interface Props {firstName: stringlastName: string
}interface Emits {(e: 'update:firstName', value: string): void(e: 'update:lastName', value: string): void
}defineProps<Props>()
defineEmits<Emits>()
</script>

父组件:

<template><div><UserFormv-model:firstName="firstName"v-model:lastName="lastName"/><p>全名: {{ firstName }} {{ lastName }}</p></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import UserForm from './UserForm.vue'const firstName = ref<string>('')
const lastName = ref<string>('')
</script>

2.3 带类型的模型

子组件:

<template><div class="number-input"><button @click="decrement">-</button><input:value="modelValue"@input="handleInput"type="number"><button @click="increment">+</button></div>
</template><script setup lang="ts">
interface Props {modelValue: numbermin?: numbermax?: number
}interface Emits {(e: 'update:modelValue', value: number): void
}const props = withDefaults(defineProps<Props>(), {min: 0,max: 100
})const emit = defineEmits<Emits>()const handleInput = (event: Event) => {const value = Number((event.target as HTMLInputElement).value)emit('update:modelValue', Math.min(Math.max(value, props.min), props.max))
}const increment = () => {const newValue = Math.min(props.modelValue + 1, props.max)emit('update:modelValue', newValue)
}const decrement = () => {const newValue = Math.max(props.modelValue - 1, props.min)emit('update:modelValue', newValue)
}
</script>

2.4 修饰符处理

子组件:

<template><input:value="modelValue"@input="handleInput":class="{ 'capitalized': hasCapitalizeModifier }">
</template><script setup lang="ts">
interface Props {modelValue: stringmodelModifiers?: {capitalize?: booleantrim?: boolean}
}interface Emits {(e: 'update:modelValue', value: string): void
}const props = withDefaults(defineProps<Props>(), {modelModifiers: () => ({})
})const emit = defineEmits<Emits>()const hasCapitalizeModifier = props.modelModifiers.capitalizeconst handleInput = (event: Event) => {let value = (event.target as HTMLInputElement).valueif (props.modelModifiers.capitalize) {value = value.charAt(0).toUpperCase() + value.slice(1)}if (props.modelModifiers.trim) {value = value.trim()}emit('update:modelValue', value)
}
</script>

父组件使用修饰符:

<template><div><CustomInput v-model.capitalize.trim="textValue" /><p>处理后的文本: "{{ textValue }}"</p></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'const textValue = ref<string>('')
</script>

3. Vue 3.4 之后的新 v-model 用法 (TypeScript)

3.1 defineModel() 宏的引入

基本用法:

<template><input v-model="model">
</template><script setup lang="ts">
// 自动推断为 Ref<string>
const model = defineModel<string>()
</script>

3.2 新旧写法对比

特性Vue 3.4 之前Vue 3.4 之后
声明defineProps() + defineEmits()defineModel()
类型定义需要定义 Props 和 Emits 接口自动类型推断或简单泛型
代码量较多样板代码极简

3.3 详细用法对比

3.3.1 基本输入框组件

Vue 3.4 之前:

<template><input:value="modelValue"@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)":placeholder="placeholder">
</template><script setup lang="ts">
interface Props {modelValue: stringplaceholder?: string
}interface Emits {(e: 'update:modelValue', value: string): void
}defineProps<Props>()
defineEmits<Emits>()
</script>

Vue 3.4 之后:

<template><inputv-model="model":placeholder="placeholder">
</template><script setup lang="ts">
interface Props {placeholder?: string
}const model = defineModel<string>('modelValue', { required: true })
defineProps<Props>()
</script>
3.3.2 带类型的模型

Vue 3.4 之前:

<template><input:value="modelValue"@input="handleInput"type="number">
</template><script setup lang="ts">
interface Props {modelValue: number
}interface Emits {(e: 'update:modelValue', value: number): void
}const props = defineProps<Props>()
const emit = defineEmits<Emits>()const handleInput = (event: Event) => {const value = Number((event.target as HTMLInputElement).value)emit('update:modelValue', value)
}
</script>

Vue 3.4 之后:

<template><inputv-model="model"type="number">
</template><script setup lang="ts">
// 自动处理类型转换,model 的类型是 Ref<number>
const model = defineModel<number>()
</script>

3.4 多个 v-model 绑定

Vue 3.4 之前:

<template><div class="user-form"><input:value="firstName"@input="$emit('update:firstName', ($event.target as HTMLInputElement).value)"placeholder="名"><input:value="lastName"@input="$emit('update:lastName', ($event.target as HTMLInputElement).value)"placeholder="姓"></div>
</template><script setup lang="ts">
interface Props {firstName: stringlastName: string
}interface Emits {(e: 'update:firstName', value: string): void(e: 'update:lastName', value: string): void
}defineProps<Props>()
defineEmits<Emits>()
</script>

Vue 3.4 之后:

<template><div class="user-form"><input v-model="firstNameModel" placeholder="名"><input v-model="lastNameModel" placeholder="姓"></div>
</template><script setup lang="ts">
const firstNameModel = defineModel<string>('firstName')
const lastNameModel = defineModel<string>('lastName')
</script>

3.5 修饰符处理

Vue 3.4 之前:

<template><input:value="modelValue"@input="handleInput">
</template><script setup lang="ts">
interface Props {modelValue: stringmodelModifiers?: {capitalize?: booleantrim?: boolean}
}interface Emits {(e: 'update:modelValue', value: string): void
}const props = withDefaults(defineProps<Props>(), {modelModifiers: () => ({})
})const emit = defineEmits<Emits>()const handleInput = (event: Event) => {let value = (event.target as HTMLInputElement).valueif (props.modelModifiers.capitalize) {value = value.charAt(0).toUpperCase() + value.slice(1)}if (props.modelModifiers.trim) {value = value.trim()}emit('update:modelValue', value)
}
</script>

Vue 3.4 之后:

<template><input v-model="modelValue">
</template><script setup lang="ts">
const modelValue = defineModel<string>({set(value) {if (modelValue.modifiers.capitalize) {return value.charAt(0).toUpperCase() + value.slice(1)}if (modelValue.modifiers.trim) {return value.trim()}return value}
})
</script>

3.6 高级用法

3.6.1 带默认值和验证
<template><input v-model="model">
</template><script setup lang="ts">
interface User {name: stringage: number
}const model = defineModel<User>({required: true,default: () => ({ name: '匿名', age: 18 }),validator(value: User): boolean {return value.name.length > 0 && value.age >= 0}
})
</script>
3.6.2 自定义 getter/setter
<template><input v-model="displayValue">
</template><script setup lang="ts">
import { computed } from 'vue'const model = defineModel<string>()const displayValue = computed({get: () => model.value ? model.value.toUpperCase() : '',set: (value: string) => {model.value = value.toLowerCase()}
})
</script>
3.6.3 复杂组件示例

Vue 3.4 之前:

<template><div class="range-slider"><inputtype="range":min="min":max="max":value="modelValue"@input="$emit('update:modelValue', Number(($event.target as HTMLInputElement).value))"><span>{{ modelValue }}</span></div>
</template><script setup lang="ts">
interface Props {modelValue: numbermin?: numbermax?: number
}interface Emits {(e: 'update:modelValue', value: number): void
}const props = withDefaults(defineProps<Props>(), {min: 0,max: 100
})defineEmits<Emits>()
</script>

Vue 3.4 之后:

<template><div class="range-slider"><inputtype="range":min="min":max="max"v-model="model"><span>{{ model }}</span></div>
</template><script setup lang="ts">
interface Props {min?: numbermax?: number
}const model = defineModel<number>({ required: true })
const props = withDefaults(defineProps<Props>(), {min: 0,max: 100
})
</script>

4. 实际项目示例

4.1 搜索组件迁移

Vue 3.4 之前:

<template><div class="search-box"><input:value="modelValue"@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"placeholder="搜索..."/><button @click="$emit('search', modelValue)">搜索</button></div>
</template><script setup lang="ts">
interface Props {modelValue: string
}interface Emits {(e: 'update:modelValue', value: string): void(e: 'search', value: string): void
}defineProps<Props>()
defineEmits<Emits>()
</script>

Vue 3.4 之后:

<template><div class="search-box"><input v-model="searchText" placeholder="搜索..." /><button @click="emit('search', searchText)">搜索</button></div>
</template><script setup lang="ts">
interface Emits {(e: 'search', value: string): void
}const searchText = defineModel<string>('modelValue', { required: true })
const emit = defineEmits<Emits>()
</script>

4.2 表单选择器组件

Vue 3.4 之后:

<template><div class="select-wrapper"><select v-model="selectedValue"><optionv-for="option in options":key="option.value":value="option.value">{{ option.label }}</option></select></div>
</template><script setup lang="ts">
interface Option {value: string | numberlabel: string
}interface Props {options: Option[]
}const selectedValue = defineModel<string | number>('modelValue', { required: true })
defineProps<Props>()
</script>

5. 类型安全和最佳实践

5.1 严格的类型定义

<template><input v-model="emailModel">
</template><script setup lang="ts">
// 使用联合类型
type Status = 'active' | 'inactive' | 'pending'const statusModel = defineModel<Status>()// 使用接口
interface UserSettings {theme: 'light' | 'dark'notifications: booleanlanguage: string
}const settingsModel = defineModel<UserSettings>({default: () => ({theme: 'light',notifications: true,language: 'zh-CN'})
})// 电子邮件验证
const emailModel = defineModel<string>({set(value: string) {const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/if (!emailRegex.test(value) && value !== '') {console.warn('无效的电子邮件格式')}return value}
})
</script>

5.2 异步操作处理

<template><div class="async-input"><inputv-model="localValue"@blur="saveChanges":disabled="isSaving"><span v-if="isSaving">保存中...</span></div>
</template><script setup lang="ts>
import { ref, watch } from 'vue'const model = defineModel<string>()
const isSaving = ref(false)
const localValue = ref(model.value)// 监听外部变化
watch(model, (newValue) => {localValue.value = newValue
})const saveChanges = async () => {if (localValue.value === model.value) returnisSaving.value = truetry {// 模拟 API 调用await new Promise(resolve => setTimeout(resolve, 1000))model.value = localValue.value} catch (error) {console.error('保存失败:', error)} finally {isSaving.value = false}
}
</script>

6. 总结

Vue 3.4 的 defineModel() 结合 TypeScript 带来了显著的改进:

主要优势:

  • 极简代码:大幅减少类型定义和样板代码
  • 完美类型推断:自动的类型安全和智能提示
  • 更好的开发体验:代码更简洁,维护更轻松
  • 编译时优化:更好的性能表现

TypeScript 最佳实践:

  1. 始终为 defineModel() 提供泛型类型参数
  2. 利用 TypeScript 的联合类型和接口确保类型安全
  3. 在复杂场景中使用自定义 setter 进行数据验证
  4. 合理使用默认值和验证器
http://www.dtcms.com/a/478593.html

相关文章:

  • 中山哪里有做微网站的做ppt图片用的网站
  • 『 QT 』QT窗口坐标体系详解
  • 服务器里怎么建设网站网站开发网站设计素材
  • 从多个数据源(CSV, Excel, SQL)自动整合数据
  • 智慧零售天气预知可视化监控平台
  • C++设计模式_结构型模式_享元模式Flyweight
  • 网站备案名称能重复吗微官网怎么制作
  • SpringBoot + MyBatis 注解开发入门实践
  • Java EE初阶--多线程
  • 深入理解梯度消失:从DNN到RNN的全面解析与解决方案
  • 南京电子商务网站开发公司石油化工工程建设人才招聘网站
  • 大数据实战:Python+Flask 汽车数据分析可视化系统(爬虫+线性回归预测+推荐 源码+文档)✅
  • 算法8.0
  • 网站左侧导航栏设计一个网站的建设要经过哪几个阶段
  • Java-Linux环境下查看JDK安装路径
  • 嘉立创学习
  • QML学习笔记(三十四)QML的GroupBox、RadioButton
  • AI Agent 的技术架构、产业赋能与治理挑战研究 —— 基于 2024-2025 年技术突破与应用实践的分析
  • 设计美观网站有哪些辽宁网站建设价位
  • vtkFillHolesFilter——3D网格补孔的“一键修复”工具,从原理到避坑
  • 网站建设完整代码深圳开公司流程及费用
  • Vue3为什么选择用Vite?使用指南与优势解析
  • 【STL】set容器(2336.无限集中的最小数字)
  • 第一章 计算机系统概论1
  • Cannot invoke “String.length()“ because “<parameter1>“ is null
  • H5使用环信实现视频或语音通话
  • SMTPman高效稳定的smtp服务器使用指南解析
  • 《Qt应用开发》笔记p3
  • Java-148 深入浅出 MongoDB 聚合操作:$match、$group、$project、$sort 全面解析 Pipeline 实例详解与性能优化
  • Oops 概念