You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3问题:getter/setter用不同数据类型时update:modelValue失效

Vue DatePicker组件:computed对象属性无法更新导致set函数不触发

问题背景

开发DatePicker组件需求:

  • 接收字符串格式日期(如'01/01/2023'),转换为内部处理对象:
{
  day: '01',
  month: '01',
  year: '2023'
}
  • 值变更时通过update:modelValue emit字符串格式日期

当前代码中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:40:58