Vue3问题:getter/setter用不同数据类型时update:modelValue失效
Vue DatePicker组件:computed对象属性无法更新导致set函数不触发
问题背景
开发DatePicker组件需求:
- 接收字符串格式日期(如
'01/01/2023'),转换为内部处理对象:
{ day: '01', month: '01', year: '2023' }
- 值变更时通过
update:modelValueemit字符串格式日期
当前代码中model[key]无法更新对象,导致computed的set函数不触发。
问题原因
Vue的computed属性返回对象时,直接修改对象的单个属性(如model[key] = xxx)不会触发computed的set函数——computed的set仅在整体替换该对象时才会执行。当前TextField的v-model绑定model[key],本质是修改computed返回对象的属性,并未触发set逻辑。
解决方案
方案一:用ref维护内部状态+监听同步(推荐)
改用ref存储内部日期对象,监听内部状态变化后组装日期字符串emit,同时监听props变化同步到内部状态。
修改后的完整代码:
<template> <div class="date-picker"> <label class="date-picker__label">{{ label }}</label> <div class="date-picker__inputs"> <div v-for="(key, index) in Object.keys(config)" :key="index"> <label class="date-picker__label" :for="key">{{ key }}</label> <TextField v-model="internalModel[key]" :class="key" :name="key" /> </div> </div> </div> </template> <script setup> import { ref, watch, computed } from 'vue' import TextField from './TextField.vue' const props = defineProps({ label: { type: String, default: 'Date of Birth', }, modelValue: { type: String, default: '01/01/2023', }, lang: { type: String, required: true, }, dateOptions: { type: Object, default: () => ({ year: 'numeric', month: 'numeric', day: 'numeric', }), }, }) const emit = defineEmits(['update:modelValue']) const config = computed(() => { let config = {} switch (props.lang) { case 'en-US': config = { month: {}, day: {}, year: {} } break case 'de-DE': config = { day: {}, month: {}, year: {} } break } return config }) // 存储内部日期对象的ref const internalModel = ref({ day: '', month: '', year: '' }) // 解析日期字符串到内部对象 const parseDateToModel = (dateStr) => { const d = new Date(dateStr) if (d instanceof Date && isFinite(d)) { internalModel.value = { day: d.getDate().toString().padStart(2, '0'), month: (d.getMonth() + 1).toString().padStart(2, '0'), year: d.getFullYear().toString() } } else { internalModel.value = { day: '', month: '', year: '' } } } // 监听props的modelValue变化,同步到内部状态 watch(() => props.modelValue, (newVal) => { parseDateToModel(newVal) }, { immediate: true }) // 监听内部状态变化,组装合法日期后emit watch(internalModel, (newModel) => { const { day, month, year } = newModel // 空值不处理 if (!day || !month || !year) return // 用ISO格式创建Date避免解析差异 const date = new Date(`${year}-${month}-${day}`) if (date instanceof Date && isFinite(date)) { emit( 'update:modelValue', new Intl.DateTimeFormat(props.lang, props.dateOptions).format(date) ) } }, { deep: true }) </script>
方案二:为每个字段单独创建computed
如果坚持使用computed,需要为day、month、year分别创建独立的computed,修改时整体替换computed返回的对象,从而触发set函数。示例:
<template> <!-- 以day为例 --> <TextField v-model="day" :class="day" :name="day" /> </template> <script setup> // ... 其他代码不变 const model = computed({ get: () => { // 原get逻辑不变 }, set: (val) => { // 原set逻辑不变 } }) // 单独的day computed const day = computed({ get: () => model.value.day, set: (val) => { // 整体替换对象触发model的set model.value = { ...model.value, day: val } } }) // month和year同理创建对应的computed </script>
总结
方案一更简洁直观,通过ref维护内部状态,deep watch监听属性变化,同时处理了日期解析的可靠性问题(用ISO格式创建Date),避免不同语言环境下的解析错误。
内容的提问来源于stack exchange,提问作者Riza Khan
相关产品推荐
相关产品推荐

