Code前端首页关于Code前端联系我们

Vue3中defineModel怎么设置默认值?实际场景里要注意啥?

terry 9小时前 阅读数 167 #Vue
文章标签 默认值注意事项

defineModel 是什么,解决了啥问题?

要搞懂 defineModel 设默认值的逻辑,得先明白它的作用,Vue 3.4 推出的 defineModel 是个编译宏,专门简化组件双向绑定开发,以前做子组件支持 v - model,得手动写 props 接收 modelValue,再用 emitupdate:modelValue 通知父组件更新,步骤繁琐还容易漏,现在用 defineModel 就简单了——子组件里一行代码声明,自动处理 propsemit,返回的还是响应式 ref,修改值时能自动触发父组件更新,开发效率直接提升。

设置默认值的核心思路是啥?

defineModel 加默认值,要用到它的配置项defineModel 第一个参数是绑定的“模型名”(比如父组件用 v - model:xxx,这里就填 xxx),第二个参数是对象,能配置 defaultrequired 等规则,逻辑和 defineProps 配置 props 默认值类似。

举个例子:子组件想让绑定的“搜索关键词”默认显示“请输入”,代码可以这样写:

<script setup>
// 声明 model,名为 search,默认值设为 '请输入'
const search = defineModel('search', { 
  default: '请输入' 
})
</script>
<template>
  <input :value="search" @input="search = $event.target.value" />
</template>

父组件使用时,若不传值(如 <SearchInput v - model:search />),子组件里的 search 就用 '请输入' 这个默认值;若父组件传了值(如 <SearchInput v - model:search="parentValue" />),则优先用父组件给的 parentValue

实际场景里设置默认值容易踩哪些坑?

步骤看着简单,实际开发稍不留意就会掉坑,这几个点得重点关注:

响应式与引用类型的“共享问题”

要是默认值是对象/数组这类引用类型,直接写 default: { name: '默认' } 有风险——所有子组件实例会共享同一个对象引用,一个组件改了属性,其他组件也会跟着变(因为内存里是同一个对象),这时得用工厂函数返回默认值,保证每个实例都是新对象:

const user = defineModel('user', { 
  default: () => ({ name: '匿名', age: 18 }) 
})

这样每个子组件的 user 默认值都是独立新对象,互不干扰。

父组件传值的“优先级”陷阱

要记准规则:父组件传了值,子组件默认值就不用;父组件没传(props 未被提供),才用默认值,但要是父组件传了 null 或者 undefined 呢?比如父组件写 <Child v - model:xxx="null" />,这时子组件会不会用默认值?实测发现,Vue 会把 null/undefined 当作“传了值”,默认值不会生效,要是业务里需要“传 null 时用默认值”,得在子组件里手动判断处理:

const xxx = defineModel('xxx', { default: '默认' })
// 手动处理:若父组件传了 null/undefined,强制用默认值
watch(xxx, (newVal) => {
  if (newVal == null) {
    xxx.value = '默认'
  }
})

v - model 修饰符的“类型冲突”

父组件用 v - model 修饰符时(.number 把输入转数字,.trim 去空格),子组件默认值的类型得和修饰符逻辑匹配,比如父组件用 <Child v - model:age.number="parentAge" />,子组件 age 的默认值得是 0(数字),别设成字符串 '0',否则可能出现类型不兼容引发的逻辑错误。

和传统 v - model 实现方式对比,默认值处理有啥不同?

以前写双向绑定,子组件得手动写 propsemit

<!-- 传统写法:子组件 -->
<script setup>
const props = defineProps({
  modelValue: { 
    type: String, 
    default: '请输入' 
  }
})
const emit = defineEmits(['update:modelValue'])
const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>
<template>
  <input :value="modelValue" @input="handleInput" />
</template>

现在用 defineModel,代码量直接减半,默认值配置逻辑和 defineProps 类似,但更简洁:

<!-- 新写法:子组件 -->
<script setup>
const model = defineModel('modelValue', { 
  default: '请输入' 
})
const handleInput = (e) => {
  model.value = e.target.value // 自动触发 emit
}
</script>
<template>
  <input :value="model" @input="handleInput" />
</template>

核心差异是响应式处理更自动化defineModel 返回的是 ref,修改 value 时自动发 emit;传统写法得手动调用 emit,而且代码结构更紧凑,少了 propsemit 的重复声明,降低了理解成本。

举个完整案例,从父到子设置默认值

以“搜索组件”场景为例,子组件 SearchInput 要支持默认提示文字,父组件可传可不传,同时输入变化要同步给父组件。

子组件 SearchInput.vue:

<template>
  <div class="search - wrap">
    <input 
      class="search - input" 
      :value="searchValue" 
      @input="onInput" 
      placeholder="(这里用默认值兜底)"
    />
    <button @click="clear">清空</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
// 声明 model,名为 search,默认值是 '请输入关键词'
const searchValue = defineModel('search', { 
  default: '请输入关键词' 
})
// 输入时更新 model
const onInput = (e) => {
  searchValue.value = e.target.value
}
// 清空逻辑:直接改 model 的值,自动通知父组件
const clear = () => {
  searchValue.value = ''
}
</script>
<style scoped>
.search - wrap { display: flex; }
.search - input { flex: 1; }
</style>

父组件 Parent.vue:

<template>
  <div>
    <!-- 情况1:父组件传值,子组件用父组件的 value -->
    <SearchInput v - model:search="parentSearch" />
    <p>父组件同步的值:{{ parentSearch }}</p>
    <!-- 情况2:父组件不传值,子组件用默认值 '请输入关键词' -->
    <SearchInput v - model:search />
  </div>
</template>
<script setup>
import { ref } from 'vue'
import SearchInput from './SearchInput.vue'
// 父组件自己的响应式变量
const parentSearch = ref('')
</script>

运行后能看到:第一个 SearchInput 用父组件的 parentSearch(初始空字符串),第二个因父组件没传,自动显示“请输入关键词”;输入或点“清空”时,子组件修改 searchValue.value 会自动同步给父组件,双向绑定逻辑全由 defineModel 接管,不用手动写 emit,十分便捷~

团队协作时关于默认值的规范建议

多人协作开发组件,默认值处理不统一易留隐患,这几个规范能避坑:

组件文档写清默认值逻辑

在组件的注释/文档里,明确写出 defineModel 对应的“模型名”和默认值。

<!-- SearchInput.vue 文档注释 -->
/**
 * 搜索输入框组件
 * @model search - 双向绑定的搜索关键词
 * @default search - 未传值时,默认显示 '请输入关键词'
 * @example <SearchInput v - model:search="parentVal" /> - 父组件传值
 * @example <SearchInput v - model:search /> - 用默认值
 */

其他人用组件时,看文档就知不传值的行为,减少沟通成本。

严格保持类型一致性

默认值的类型要和父组件预期类型一致,比如父组件用 v - model:age.number 期望传数字,子组件 age 的默认值就得是 0(数字类型),别写成 '0'(字符串),否则可能出现“输入框输数字,父组件拿到字符串”这类奇怪 Bug。

复杂默认值用工厂函数

若默认值是对象、数组这类引用类型,必须用工厂函数返回默认值(如前面讲的 default: () => ({ ... })),确保每个组件实例的默认值都是独立对象,避免“一个组件改值,其他组件跟着变”的共享问题。

defineModel 设置默认值的关键是利用第二个参数的 default 配置项,同时要留意引用类型的独立初始化父组件传值优先级类型与修饰符匹配这些要点,结合案例和规范,既能写得轻松(代码少),又能运行稳定(逻辑无错),团队协作也更顺畅~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门