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

Vue3中defineModel的set咋用?双向绑定逻辑得这么处理

terry 10小时前 阅读数 159 #Vue
文章标签 双向绑定

defineModel是Vue3的啥新特性?

Vue3.4版本后新增的defineModel,是给组件双向绑定做的语法糖,以前写子组件支持v-model,得手动写defineProps接收modelValue,再用defineEmits触发update:modelValue事件,现在用defineModel,一行代码能同时处理“接收父组件值”和“通知父组件更新”,返回的数组里第一个是当前值,第二个就是set函数。

举个最简例子:
父组件用<ChildComponent v-model="parentVal" />,子组件里:

<script setup>  
const [modelValue, setModelValue] = defineModel()  
// modelValue 是父组件传的parentVal当前值  
// setModelValue 是用来通知父组件更新parentVal的函数  
</script>  

defineModel里的set函数,核心作用是啥?

set函数是子组件主动修改父组件绑定值的“开关”,因为Vue的单向数据流规则,子组件不能直接改父组件传的props(虽然Vue3允许props可变,但父组件不会同步更新),所以得通过set触发一个更新事件,让父组件自己改值,再把新值传给子组件。

简单说:子组件里调用set(新值),父组件的v-model绑定值会自动更新,同时子组件里的modelValue也会同步变成新值。

咋在组件里用set修改父组件的值?举个实际场景

最常见的是自定义表单组件,比如封装一个带格式处理的输入框。

父组件用法:

<template>  
  <CustomInput v-model="username" />  
  <p>父组件的username:{{ username }}</p>  
</template>  
<script setup>  
import { ref } from 'vue'  
const username = ref('')  
</script>  

子组件CustomInput.vue实现:

<template>  
  <input  
    :value="modelValue"  
    @input="handleInput"  
    placeholder="请输入用户名"  
  />  
</template>  
<script setup>  
const [modelValue, setModelValue] = defineModel()  
function handleInput(e) {  
  const inputVal = e.target.value  
  // 假设要做格式处理:首字母大写  
  const formatted = inputVal.replace(/^./, (match) => match.toUpperCase())  
  // 调用set,把处理后的值传给父组件  
  setModelValue(formatted)  
}  
</script>  

这里关键是:输入事件里拿到用户输入,处理后用setModelValue(新值),父组件的username就会跟着变,如果直接改modelValue.value = 新值,父组件完全没反应——因为没触发更新事件,这就是set的必要性。

直接改modelValue和调用set,区别在哪?

看这段对比代码就懂了:

<script setup>  
const [modelValue, setModelValue] = defineModel()  
// 错误示范:直接改modelValue,父组件不更新  
function wrongUpdate() {  
  modelValue.value = '直接修改'  
}  
// 正确示范:调用set,父组件会更新  
function rightUpdate() {  
  setModelValue('通过set修改')  
}  
</script>  

原理是:modelValue只是“当前值的引用”,和父组件的绑定值是单向同步(父组件改了子组件会变,但子组件直接改不会通知父组件),而set内部做了两件事:

  1. 触发update:modelValue事件,把新值传给父组件;
  2. 让子组件内部的modelValue同步变成新值(因为父组件更新后会重新传值给子组件)。

多个v-model绑定,set咋处理?

如果父组件用了多个v-model(比如<Child v-model:foo="a" v-model:bar="b" />),子组件里要给defineModel传数组指定名称:

子组件代码:

<script setup>  
const [foo, setFoo] = defineModel('foo')  
const [bar, setBar] = defineModel('bar')  
function updateFoo() {  
  setFoo('新的foo值') // 触发父组件a的更新  
}  
function updateBar() {  
  setBar('新的bar值') // 触发父组件b的更新  
}  
</script>  

每个defineModel(名称)会返回对应名称的“值+set函数”,调用对应的set就只更新对应父组件的绑定值,逻辑很清晰。

和传统v-model实现比,set优势在哪?

传统写法(Vue3.4前)得手动写props和emit,代码冗余:

<script setup>  
const props = defineProps(['modelValue'])  
const emit = defineEmits(['update:modelValue'])  
function handleChange(newVal) {  
  emit('update:modelValue', newVal) // 手动触发事件  
}  
</script>  

defineModel后,props、emit、set函数全被自动处理,代码量少一半,还避免了“忘记写emit导致更新失败”的 Bug,而且set把“触发更新”的逻辑封装成函数,可读性更强——看setModelValue(xxx)就知道这是要改父组件的值,不用再翻找emit的逻辑。

用set时容易踩的3个坑,咋避?

  1. 忘记调用set,直接改modelValue
    新手常犯:以为modelValue是响应式的,改了就生效,子组件改父组件值必须走set,直接改只有自己能看到,父组件完全没变化。

  2. set的参数类型和父组件绑定值不匹配
    比如父组件绑的是数字v-model="age"(age是number),子组件set的时候传了字符串setModelValue('18'),父组件的age会变成字符串,可能导致后续逻辑报错,要保证set的参数类型和父组件绑定值一致。

  3. 异步操作中调用set,时机不对
    比如在setTimeout里调用set,要确保组件还没被卸载,可以用onBeforeUnmount清理定时器,或者用watch监听异步结果再调用set。

结合computed或watch用set,咋玩?

场景1:给modelValue加“读写逻辑”
比如父组件传的是原始值,子组件要做格式化,同时修改时要还原格式,用computed封装:

<script setup>  
const [modelValue, setModelValue] = defineModel()  
// 假设父组件传的是“2024-10-01”,子组件显示“2024/10/01”,修改后再转成“-”格式  
const formattedDate = computed({  
  get() {  
    return modelValue.replace(/-/g, '/')  
  },  
  set(newVal) {  
    const rawVal = newVal.replace(/\//g, '-')  
    setModelValue(rawVal) // 转成父组件需要的格式,再调用set  
  }  
})  
</script>  
<template>  
  <input v-model="formattedDate" />  
</template>  

场景2:watch监听modelValue变化,再调用set做联动
比如子组件里有两个关联的v-model,改一个要同步改另一个:

<script setup>  
const [foo, setFoo] = defineModel('foo')  
const [bar, setBar] = defineModel('bar')  
watch(foo, (newFoo) => {  
  // 假设foo和bar有数学关系:bar = foo * 2  
  setBar(newFoo * 2)  
})  
watch(bar, (newBar) => {  
  setFoo(newBar / 2)  
})  
</script>  

这里要注意避免循环更新:比如foo改了触发bar改,bar改了又触发foo改,会无限循环,实际项目里要加条件判断,只在必要时更新。

set在组合式函数里咋复用逻辑?

很多组件有相似的“双向绑定+逻辑处理”需求,输入验证+格式处理”,可以把set封装到组合式函数里:

// useFormInput.js  
export function useFormInput(setFn) {  
  const handleInput = (e) => {  
    const val = e.target.value  
    // 通用逻辑:去空格、验证长度等  
    const trimmed = val.trim()  
    if (trimmed.length > 10) return // 长度限制  
    setFn(trimmed) // 调用组件里的set函数  
  }  
  return { handleInput }  
}  

子组件里用:

<script setup>  
const [modelValue, setModelValue] = defineModel()  
const { handleInput } = useFormInput(setModelValue) // 把set传给组合式函数  
</script>  
<template>  
  <input @input="handleInput" :value="modelValue" />  
</template>  

这样逻辑和组件解耦,多个组件要做输入验证时,直接复用useFormInput,传各自的set函数就行。

掌握set,让双向绑定更丝滑

defineModelset函数,本质是Vue给子组件“安全修改父组件值”的官方解决方案——既遵守单向数据流,又简化了代码,记住这几点:

  • 改父组件值必须调用set,不能直接改modelValue;
  • 多个v-model对应多个set,名字要和defineModel的参数一致;
  • 结合computed、watch、组合式函数时,注意数据流向和类型一致;
  • 避开“忘调用set、类型不匹配、异步更新”这几个坑。

实际项目里,不管是封装表单组件、联动组件,还是做复杂的双向绑定逻辑,set都能让代码更简洁易维护,多写几个自定义组件练手,自然就熟了~

版权声明

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

热门