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

Vue3组合式API迁移:如何复用defineProps、defineEmits与defineExpose?

Vue3组合式API下复用defineProps/defineEmits/defineExpose及组件继承的实现

我正在将一个中型应用从Vue 2迁移至Vue 3,为此将所有代码重构为组合式API(Composition API)。我认可组合式API,但未发现其在简化代码上的明显优势,部分场景下代码反而更复杂冗长,希望是我的误解。

这种情况在重构混入(mixins)时尤为显著:使用mixins时只需引入即可获得全部所需功能,而使用组合式函数(composables)则需了解其内部实现以提取特定功能。

我的核心问题在于如何复用defineProps()、defineEmits()和defineExpose()。我的应用中有一系列输入组件,例如:

  • InputText:仅支持文本输入
  • InputNumber:复用InputText,限制仅输入数字
  • InputMoney:复用InputNumber,添加货币格式化
  • InputNumberRange:复用两个InputNumber组件
  • 等等

这些输入组件逻辑高度相似,在Vue2中仅需一个InputMixin及少量覆写即可实现复用。但在组合式API下,我不知如何实现如此简洁的复用方式,请问如何在这些组件间复用defineProps()、defineEmits()和defineExpose()?是否可以使用类似extends: MyComponent的继承方式?


解决方案

1. 组件继承:extends在Vue3中仍然有效

Vue 3保留了组件继承能力,你可以像Vue2一样用extends选项复用基础组件的props、emits、方法甚至模板逻辑,这是最贴近Vue2 mixin复用体验的方式。

示例:

<!-- InputText.vue -->
<script setup>
const props = defineProps({
  modelValue: String,
  placeholder: String,
  disabled: Boolean
})

const emit = defineEmits(['update:modelValue'])

const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}

defineExpose({
  focus: () => { /* 实现聚焦逻辑 */ }
})
</script>
<!-- InputNumber.vue -->
<script setup>
import InputText from './InputText.vue'

// 通过extends继承InputText的所有声明
export default {
  extends: InputText
}

// 扩展数字专属props
const props = defineProps({
  min: Number,
  max: Number
})

const emit = defineEmits(['update:modelValue'])

// 覆写InputText的输入处理逻辑,添加数字校验
const handleInput = (e) => {
  const value = e.target.value.replace(/[^0-9]/g, '')
  if (props.min !== undefined && Number(value) < props.min) {
    emit('update:modelValue', props.min.toString())
  } else if (props.max !== undefined && Number(value) > props.max) {
    emit('update:modelValue', props.max.toString())
  } else {
    emit('update:modelValue', value)
  }
}
</script>

注意:<script setup>中需通过export default单独声明extends,因为<script setup>是编译时语法,不会直接暴露组件选项。这种方式能快速复用基础组件的所有能力,同时按需覆写或扩展。

2. 封装可复用逻辑:组合式函数+类型工具

如果更偏向组合式API风格,可以将通用的props、emits定义封装成函数,在子组件中导入并扩展,结合Vue3类型系统还能保证类型安全。

步骤1:封装通用props/emits

// composables/useInputCommon.js
export const useInputProps = () => ({
  modelValue: {
    type: [String, Number],
    default: ''
  },
  placeholder: {
    type: String,
    default: ''
  },
  disabled: {
    type: Boolean,
    default: false
  }
})

export const useInputEmits = () => ['update:modelValue', 'blur', 'focus']

步骤2:在组件中复用并扩展

<!-- InputText.vue -->
<script setup>
import { useInputProps, useInputEmits } from '../composables/useInputCommon'

const props = defineProps(useInputProps())
const emit = defineEmits(useInputEmits())

const handleInput = (e) => {
  emit('update:modelValue', e.target.value)
}

const handleBlur = (e) => emit('blur', e)
const handleFocus = (e) => emit('focus', e)

defineExpose({
  focus: () => { /* 实现聚焦 */ }
})
</script>
<!-- InputNumber.vue -->
<script setup>
import { useInputProps, useInputEmits } from '../composables/useInputCommon'

// 复用通用props并添加数字专属配置
const props = defineProps({
  ...useInputProps(),
  min: {
    type: Number,
    default: -Infinity
  },
  max: {
    type: Number,
    default: Infinity
  }
})

const emit = defineEmits(useInputEmits())

const handleInput = (e) => {
  const rawValue = e.target.value.replace(/[^0-9.-]/g, '')
  const value = Number(rawValue) || ''
  if (value >= props.min && value <= props.max) {
    emit('update:modelValue', value)
  }
}

// 封装暴露方法的复用逻辑
const focus = () => { /* 复用或重写聚焦逻辑 */ }
defineExpose({ focus })
</script>

若要复用defineExpose内容,可将暴露的方法封装到组合式函数:

// composables/useInputCommon.js
export const useInputExpose = (inputRef) => {
  const focus = () => inputRef.value?.focus()
  return { focus }
}

组件中使用:

<script setup>
import { ref } from 'vue'
import { useInputExpose } from '../composables/useInputCommon'

const inputRef = ref(null)
const exposed = useInputExpose(inputRef)

defineExpose(exposed)
</script>

3. 嵌套复用场景:组件引入+组合式函数

对于InputNumberRange这类需要复用多个子组件的场景,可直接引入组件并通过组合式函数共享逻辑:

<!-- InputNumberRange.vue -->
<script setup>
import InputNumber from './InputNumber.vue'
import { useInputProps } from '../composables/useInputCommon'

const props = defineProps({
  ...useInputProps(),
  startValue: Number,
  endValue: Number
})

const emit = defineEmits(['update:startValue', 'update:endValue'])
</script>

<template>
  <div class="range-input">
    <InputNumber 
      :model-value="startValue"
      @update:model-value="emit('update:startValue', $event)"
      :min="props.min"
      :max="props.max"
    />
    <span>至</span>
    <InputNumber 
      :model-value="endValue"
      @update:model-value="emit('update:endValue', $event)"
      :min="props.min"
      :max="props.max"
    />
  </div>
</template>

组合式API优势补充

你觉得组合式API在部分场景更冗长,可能是初期重构时未找到合适的封装方式。相比mixins,它的核心优势在于:

  • 明确的依赖关系:能清晰看到组件用到的复用逻辑,避免mixins的命名冲突和隐式依赖
  • 更好的类型支持:配合TypeScript,组合式函数的类型推导更清晰,而mixins的类型处理复杂
  • 灵活的逻辑组合:可按需组合多个小的组合式函数,而非像mixins一样一次性引入所有逻辑

对于你的输入组件系列,推荐先尝试extends继承方式快速迁移现有逻辑,之后再逐步将通用逻辑拆解为组合式函数,实现更细粒度的复用。

内容的提问来源于stack exchange,提问作者Zalo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 00:05:21