透传时的类型声明

完整导入一个组件的所有属性可借由透传实现

透传 Attributes | Vue.js

也可以控制非props属性不透传

对于一个完善的组件(比如element-plusprime-vue)往往具有完备的类型声明,在透传时同样希望完整映射原组件的类型

vue.js中,defineProps不支持复杂类型(比如动态类型推导Record[K in keyof T]),如下:

// popover-items.ts
import type { ElPopover } from 'element-plus'
import type { RawProps } from './popover-items-type'

export const popoverItemsProps = {
// 新增 props
// foo: {
// type: String,
// default: 'foo',
// },
} as const

// 自动继承 ElPopover 所有 props + 合并新增 props
export type PopoverItemsProps = RawProps

// 自动继承 ElPopover emits + 合并新增 emits
export type PopoverItemsEmits
  = InstanceType<typeof ElPopover>['$emit']
    & ((event: 'select', idx: number, vnode: any) => void)

export const popoverItemsEmits = {
  select: (idx: number, _vNode: any) => !Number.isNaN(idx),
} as const
// popover-items.vue
const props = defineProps<Partial<PopoverItemsProps>>()
// popover-items-type.d.ts
import type { PopoverProps } from 'element-plus'
import type { ExtractPropTypes } from 'vue'
import type { popoverItemsProps } from './popover-items'

export type RawProps
  = PopoverProps
    & ExtractPropTypes<typeof popoverItemsProps>

打包时将会提示[vite:vue] [@vue/compiler-sfc] Unresolvable type: TSTypeQuery 尽管此时类型已经完全交由ts生成

解决方式

  1. 显示定义: 对于一个属性较少、类型较简单的组件完全足够
  2. 完整映射:

使用element-plus定义的元组,进行组合

import type { PopoverEmits, PopoverProps } from 'element-plus'
// import type { PropType } from 'vue'
import { popoverEmits, popoverProps } from 'element-plus'

// 直接继承 Element Plus 的运行时 props 定义
export const popoverItemsProps = {
  ...popoverProps,
  // 新增 props
  // items: {
  //   type: Array as PropType<Array<{ label: string, value: any }>>,
  //   default: () => [],
  // },
} as const

// 直接继承 Element Plus 的运行时 emits 定义
export const popoverItemsEmits = {
  ...popoverEmits,
  // 新增 emits
  select: (idx: number, _vNode: any) => !Number.isNaN(idx),
} as const

// 类型定义
export type PopoverItemsProps = PopoverProps & {
  items?: Array<{ label: string, value: any }>
}

export type PopoverItemsEmits = PopoverEmits & {
  select: [idx: number, vnode: any]
}

对于不存在的属性元组,可以尝试访问实例的属性

import type { PopoverProps } from 'element-plus'
import type { PropType } from 'vue'
// popover-items.ts
import { ElPopover } from 'element-plus'

// 从组件实例获取 props 定义
const elPopoverProps = (ElPopover as any).props ?? {}
const elPopoverEmits = (ElPopover as any).emits ?? {}

export const popoverItemsProps = {
  ...elPopoverProps,
  // 新增 props
  items: {
    type: Array as PropType<Array<{ label: string, value: any }>>,
    default: () => [],
  },
} as const

export const popoverItemsEmits = {
  ...elPopoverEmits,
  select: (idx: number, _vNode: any) => !Number.isNaN(idx),
} as const

export type PopoverItemsProps = PopoverProps & {
  items?: Array<{ label: string, value: any }>
}