刘勇虎的官方网站
网站内容包含大前端、服务器开发、Python开发、iOS开发、Android开发、网站维护等技术文章。专注于分享技术经验,职业心得体会,IT优秀文章与教程创作。
Stay hungry,Stay foolish,Stay young
目录:
在 Vue 3.5 版本中引入的 defineModel
是一种简化父子组件之间双向绑定的方式,它替代了 Vue 2 中的 .sync
和 Vue 3 中手动使用 v-model
的方式。然而,在实际开发中我发现 defineModel
在某些场景下存在一定的局限性。
当子组件使用 defineModel
接收父组件传递的响应式数据时,如果直接操作数组索引(如 model.value[index] = newValue
),Vue 并不会触发视图更新。这主要是因为 defineModel
返回的是一个 ref 包装后的响应式引用,而数组的索引赋值并不会触发 ref 的 .value
变化,因此无法被 Vue 检测到。
此外,若父组件传入的属性本身是非响应式的(例如通过 toRaw
或 JSON.parse(JSON.stringify(...))
转换后的对象),也会导致子组件中的 defineModel
无法正确追踪变化,从而影响视图更新。
为了确保 Vue 能检测到变化,可以采用“重新赋值”的方式来更新整个数组或对象:
<script setup>
const model = defineModel('model', [])
// 修改某个元素的值后,重新赋值整个数组
model.value[index] = value;
model.value = [...model.value]; // 触发响应式更新
</script>
这种方式适用于数组类型的数据,通过展开运算符创建新数组,触发 Vue 的响应式机制。
watch
监听外部 props 变化并同步本地状态如果你需要对传入的非响应式数据进行处理,可以在子组件中监听 model
的变化,并将其同步到本地响应式变量中:
<script setup>
import { ref, watch } from 'vue';
const model = defineModel('model');
const localModel = ref(model.value);
watch(
() => model.value,
(newVal) => {
localModel.value = newVal;
}
);
// 修改 localModel 后再同步回 model
function updateItem(index, value) {
localModel.value[index] = value;
model.value = [...localModel.value];
}
</script>
这样即使父组件传入的是非响应式数据,也可以保证子组件内部状态是响应式的。
ref
+ v-model
显式管理对于更复杂的场景,可以考虑不使用 defineModel
,而是显式地通过 props
和 emit
来管理模型值,这样可以获得更大的控制权:
<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
const localValue = ref(props.modelValue);
watch(localValue, (newVal) => {
emit('update:modelValue', newVal);
});
</script>
场景 | 建议做法 |
---|---|
数组修改 | 使用 [...arr] 创建新数组后再赋值 |
对象修改 | 使用 Object.assign({}, obj) 或 { ...obj } |
非响应式输入 | 使用 watch 监听并同步至本地 ref |
复杂交互 | 显式使用 props + emit 替代 defineModel |
虽然 defineModel
提供了一种简洁的方式来实现父子组件之间的双向绑定,但在处理数组、对象等复杂结构时仍需注意其响应式限制。通过合理使用响应式赋值、监听器以及显式事件通信,可以有效规避这些限制,提升组件间通信的稳定性和可维护性。