Vue 3.3 改进之defineModel 宏

在 Vue 3 中,defineModel 宏 是 Vue 3.3(2023 年 5 月发布)引入的一项新特性,旨在简化组件中 v-model 双向绑定的实现。它是 <script setup> 语法糖的一部分,提供了更直观、简洁的方式来定义组件的模型值(model value),减少样板代码,同时保持类型安全和响应式特性。


什么是 defineModel 宏?

  • defineModel 是一个专为 <script setup> 设计的宏,用于声明组件的 v-model 绑定。
  • 它简化了传统方式中需要手动定义 props(如 modelValue)和 emits(如 update:modelValue)的繁琐步骤。
  • 作用:允许组件直接声明一个响应式的模型值,自动处理父子组件的双向数据同步。
背景
  • 在 Vue 3 中,v-model 是一种常见的双向绑定机制,通常用于表单组件(如输入框、开关)或自定义组件。
  • Vue 2 和 Vue 3.2 之前的实现需要显式声明 props 和 emits:
    <script> 
    export default { 
    props: ['modelValue'], 
    emits: ['update:modelValue'], 
    setup(props, { emit }) { 
    const updateValue = (value) => { 
    emit('update:modelValue', value); 
    }; 
    return { updateValue }; }, }; 
    </script>

  • 这种方式代码冗长,尤其在 <script setup> 中,Vue 3.3 通过 defineModel 极大简化了流程。

核心特性

  1. 简洁性
    • 无需手动声明 props 和 emits,defineModel 自动生成 modelValue 属性和 update:modelValue 事件。
  2. 响应式
    • 返回一个 Ref 类型的响应式引用,类似于 ref,可直接在组件中读写。
  3. 类型安全
    • 与 TypeScript 集成良好,支持类型推导和校验。
  4. 灵活性
    • 支持自定义模型名称(例如 v-model:foo)和默认值/校验逻辑。

基本用法

1. 默认 v-model

在 <script setup> 中,使用 defineModel 声明默认的 modelValue:

<script setup>
import { defineModel } from 'vue';

// 声明 modelValue
const modelValue = defineModel();
</script>

<template>
  <input v-model="modelValue" />
</template>
  • 说明
    • modelValue 是一个响应式的 Ref 对象,可直接用于模板的 v-model。
    • 父组件使用 v-model 绑定:
      <template>
        <ChildComponent v-model="parentValue" />
      </template>
      <script setup>
      import { ref } from 'vue';
      import ChildComponent from './ChildComponent.vue';
      const parentValue = ref('初始值');
      </script>

    • 子组件的 modelValue 与父组件的 parentValue 自动同步。
2. 自定义模型名称

支持自定义 v-model 的名称(例如 v-model:foo):

<script setup>
const foo = defineModel('foo');
</script>

<template>
  <input v-model="foo" />
</template>
  • 父组件使用:

    <ChildComponent v-model:foo="parentFoo" />

  • 说明:foo 对应子组件的 foo 属性和 update:foo 事件。
3. 设置默认值和校验

支持为模型值设置默认值或校验规则:

<script setup>
const modelValue = defineModel({
  default: 0, // 默认值
  set(value) {
    return value >= 0 ? value : 0; // 校验:确保值非负
  },
});
</script>

<template>
  <input type="number" v-model="modelValue" />
</template>
  • 说明
    • default:指定默认值,当父组件未提供值时使用。
    • set:自定义 setter 函数,用于值校验或转换。
4. TypeScript 支持

defineModel 与 TypeScript 集成良好,支持类型声明:

<script setup lang="ts">
const modelValue = defineModel<string>({ default: '' });
</script>

<template>
  <input v-model="modelValue" />
</template>
  • 说明:通过泛型 <string> 指定 modelValue 的类型,确保类型安全。

高级用法

1. 多个 v-model

支持组件定义多个 v-model 绑定:

<script setup>
const name = defineModel('name', { default: '' });
const age = defineModel('age', { default: 18 });
</script>

<template>
  <input v-model="name" placeholder="姓名" />
  <input type="number" v-model="age" />
</template>
  • 父组件:

    <ChildComponent v-model:name="parentName" v-model:age="parentAge" />

2. 结合计算属性

通过计算属性对模型值进行转换:

<script setup>
import { computed } from 'vue';
const modelValue = defineModel<string>({ default: '' });
const upperCaseValue = computed({
  get: () => modelValue.value.toUpperCase(),
  set: (value) => (modelValue.value = value.toLowerCase()),
});
</script>

<template>
  <input v-model="upperCaseValue" />
</template>
  • 说明:父组件绑定小写值,子组件显示大写值。
3. 与其他 API 结合

结合 watch 或 watchEffect 监听模型值变化:

<script setup>
import { watch } from 'vue';
const modelValue = defineModel<string>({ default: '' });
watch(modelValue, (newValue) => {
  console.log('modelValue 变为:', newValue);
});
</script>

<template>
  <input v-model="modelValue" />
</template>

优点

  1. 减少样板代码
    • 无需手动定义 props 和 emits,代码更简洁。
    • 对比传统方式:

      // 传统方式  defineProps(['modelValue']);  defineEmits(['update:modelValue']);// defineModel const modelValue = defineModel();

  2. 响应式集成
    • 自动返回 Ref 类型,支持响应式更新,无需额外 ref 包装。
  3. 类型安全
    • 与 TypeScript 深度集成,减少类型错误,适合复杂 H5 项目。
  4. 灵活性
    • 支持自定义模型名称、默认值和校验,适应多样化场景。

注意事项

  1. 仅限 <script setup>
    • defineModel 只能在 <script setup> 中使用,不支持选项式 API 或普通 setup 函数。
  2. 父组件必须使用 v-model
    • 如果父组件未绑定 v-model,defineModel 的默认值生效,但不会触发更新。
  3. 性能
    • defineModel 的响应式开销与 ref 相同,通常可忽略,但在高频更新场景中需注意。
  4. 跨域 <iframe>
    • 在 <iframe> 嵌套场景中,defineModel 仅控制父组件状态,无法直接操作 <iframe> 内部 DOM,需结合 postMessage 通信。

与传统方式的对比

特性传统方式 (props + emits)defineModel
代码量较多(需定义 props 和 emits)简洁(单行声明)
响应式需手动结合 ref 或 computed自动返回 Ref 类型
类型推导需显式声明类型内置类型推导,TS 友好
自定义模型名称支持,但需手动处理内置支持(如 defineModel('foo'))
默认值/校验需在 props 中定义内置支持 default 和 set

实际案例:H5 表单组件

以下是一个完整的 H5 表单组件示例,使用 defineModel 实现用户名输入框,结合游客登录场景:

<!-- GuestInput.vue -->
<script setup lang="ts">
import { defineModel } from 'vue';
const username = defineModel<string>('username', {
  default: '',
  set(value) {
    return value.trim(); // 去除首尾空格
  },
});
</script>

<template>
  <div class="input-box">
    <input v-model="username" placeholder="请输入用户名" />
    <button @click="username = `Guest_${Date.now()}`">生成游客名</button>
  </div>
</template>

<style>
.input-box {
  display: flex;
  gap: 10px;
}
</style>
  • <!-- Parent.vue -->
    <script setup lang="ts">
    import { ref } from 'vue';
    import GuestInput from './GuestInput.vue';
    const guestUsername = ref('');
    </script>
    
    <template>
      <GuestInput v-model:username="guestUsername" />
      <p v-if="guestUsername">当前用户名: {{ guestUsername }}</p>
    </template>

  • 运行效果
    • 用户输入用户名或点击按钮生成游客名,父组件的 guestUsername 实时同步。
    • 输入值自动去除首尾空格(通过 set 校验)。

总结

defineModel 是 Vue 3.3 引入的强大宏,极大简化了 <script setup> 中 v-model 的实现,减少了样板代码,提升了开发效率和类型安全。其在 H5 网站开发中的优势包括:

  • 简洁性:单行声明双向绑定,适合快速开发表单或输入组件。
  • 响应式:内置 Ref 类型,结合 watch、computed 等 API 实现动态逻辑。
  • H5 场景:可用于游客登录表单、rParam 输入框、<iframe> 属性控制等。
  • TypeScript:增强类型推导,适合复杂项目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值