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

Vue使用extend继承组件时,如何访问带类型的Emits及复用资源?

Vue3 实现组件复用:替代 Vue2 extend 方案

我有两个功能高度相似的搜索组件,它们共享相同的 props、refs、watchers 和 emitter。目前通过 defineComponent 继承基础组件后,仅能访问 props,但带类型的 emits、基础组件的 ref 和 watcher 都无法正确复用,希望复刻 Vue2 的 extend 功能来减少重复代码。

基础组件代码(原版本)

<script setup lang="ts">
import { ref, watch } from 'vue'

import type { OptionType } from './BaseSearch.types'

export interface SearchEmits {
    (event: 'update:modelValue', val: OptionType[]): void
    (event: 'input', val: string): void
}

const emit = defineEmits<SearchEmits>()

const props = withDefaults(
    defineProps<{
        closeOnSelect?: boolean
        disabled?: boolean
        errorMessage?: string
        isError?: boolean
        isLoading?: boolean
        label?: string
        modelValue: OptionType[]
        options: OptionType[]
        placeholder?: string
        required?: boolean
        searchable?: boolean
    }>(),
    {
        required: false,
        searchable: true,
    }
)

const input = ref('')

watch(input, () => {
    emit('input', input.value)
})
</script>

继承组件代码(原版本)

<template>
  <!-- 自定义模板已省略 -->
</template>

<script lang="ts">
import { defineComponent, PropType, ref, watch } from 'vue'
import MultiSelect from 'MultiSelect'
import BaseSearch, { SearchEmits } from '../BaseSearch/BaseSearch.vue'

export default defineComponent({
    components: { MultiSelect },
    extends: BaseSearch,
    props: {
        // 扩展新props
        canClear: {
            required: false,
            type: Boolean,
        },
    },
    setup(props, ctx) {
        const emit: SearchEmits = ctx.emit

        // 期望直接复用基础组件的input ref
        const input = ref('')
        // 期望直接复用基础组件的watcher
        watch(input, () => {
            emit('input', input.value)
        })

        return { emit, highlightMatchedTerm, input, props }
    },
})
</script>

解决方案

方案一:通过 defineExpose 配合 extends 实现继承复用

Vue3 的 <script setup> 默认是封闭作用域,内部变量无法被外部访问,需要手动通过 defineExpose 暴露需要复用的资源。

1. 修改基础组件,暴露资源并封装复用逻辑

<script setup lang="ts">
import { ref, watch } from 'vue'
import type { OptionType } from './BaseSearch.types'

export interface SearchEmits {
    (event: 'update:modelValue', val: OptionType[]): void
    (event: 'input', val: string): void
}

// 导出props类型方便子组件复用
export interface BaseSearchProps {
    closeOnSelect?: boolean
    disabled?: boolean
    errorMessage?: string
    isError?: boolean
    isLoading?: boolean
    label?: string
    modelValue: OptionType[]
    options: OptionType[]
    placeholder?: string
    required?: boolean
    searchable?: boolean
}

const emit = defineEmits<SearchEmits>()

const props = withDefaults(defineProps<BaseSearchProps>(), {
    required: false,
    searchable: true,
})

const input = ref('')

// 将watch逻辑封装为可复用函数
export const setupInputWatcher = (inputRef: typeof input, emitInstance: typeof emit) => {
    return watch(inputRef, () => {
        emitInstance('input', inputRef.value)
    })
}

// 执行默认watch逻辑
setupInputWatcher(input, emit)

// 暴露input ref给子组件访问
defineExpose({
    input,
})
</script>

2. 修改继承组件,复用基础资源

<template>
  <!-- 自定义模板 -->
</template>

<script lang="ts">
import { defineComponent, getCurrentInstance } from 'vue'
import MultiSelect from 'MultiSelect'
import BaseSearch, { SearchEmits, BaseSearchProps, setupInputWatcher } from '../BaseSearch/BaseSearch.vue'

export default defineComponent({
    components: { MultiSelect },
    extends: BaseSearch,
    props: {
        // 扩展新props
        canClear: {
            required: false,
            type: Boolean,
        },
        // 复用基础组件的props类型
        ...BaseSearchProps,
    },
    setup(props, ctx) {
        // 给emit添加类型断言
        const emit = ctx.emit as SearchEmits
        
        // 获取基础组件实例,访问暴露的input ref
        const baseInstance = getCurrentInstance()?.parent
        const input = baseInstance?.exposed?.input
        
        // 复用基础组件的watch逻辑
        if (input) {
            setupInputWatcher(input, emit)
        }

        // 自定义业务逻辑
        const highlightMatchedTerm = () => {
            // 你的自定义实现
        }

        return { emit, highlightMatchedTerm, input, props }
    },
})
</script>

方案二:使用组合式函数(Composables)替代继承(推荐)

Vue3 更推荐用组合式 API 封装复用逻辑,相比继承更灵活,也更符合框架设计理念。

1. 创建组合式函数 useSearch.ts

import { ref, watch } from 'vue'
import type { OptionType } from './BaseSearch.types'

export interface SearchEmits {
    (event: 'update:modelValue', val: OptionType[]): void
    (event: 'input', val: string): void
}

export interface SearchProps {
    closeOnSelect?: boolean
    disabled?: boolean
    errorMessage?: string
    isError?: boolean
    isLoading?: boolean
    label?: string
    modelValue: OptionType[]
    options: OptionType[]
    placeholder?: string
    required?: boolean
    searchable?: boolean
}

export function useSearch(props: SearchProps, emit: SearchEmits) {
    const input = ref('')

    watch(input, () => {
        emit('input', input.value)
    })

    return {
        input,
    }
}

2. 基础组件使用组合式函数

<script setup lang="ts">
import { withDefaults, defineProps, defineEmits } from 'vue'
import { useSearch, SearchProps, SearchEmits } from './useSearch'

const props = withDefaults(defineProps<SearchProps>(), {
    required: false,
    searchable: true,
})

const emit = defineEmits<SearchEmits>()

// 复用逻辑
const { input } = useSearch(props, emit)
</script>

3. 子组件扩展并复用逻辑

<template>
  <!-- 自定义模板 -->
</template>

<script setup lang="ts">
import { withDefaults, defineProps, defineEmits } from 'vue'
import MultiSelect from 'MultiSelect'
import { useSearch, SearchProps, SearchEmits } from '../useSearch'

// 扩展基础props类型
interface ExtendedSearchProps extends SearchProps {
    canClear?: boolean
}

const props = withDefaults(defineProps<ExtendedSearchProps>(), {
    canClear: false,
    // 继承基础props的默认值
    required: false,
    searchable: true,
})

const emit = defineEmits<SearchEmits>()

// 复用核心逻辑
const { input } = useSearch(props, emit)

// 自定义业务逻辑
const highlightMatchedTerm = () => {
    // 你的自定义实现
}
</script>

说明

  • 方案一通过 defineExpose 打破 <script setup> 的封闭性,配合 extends 实现类似 Vue2 的继承效果,但需要注意实例访问的稳定性。
  • 方案二是 Vue3 官方推荐的复用方式,组合式函数可以灵活地在多个组件中共享逻辑,避免继承带来的耦合问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 00:01:16