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

Vue3+TS通用Tile组件Storybook类型错误修复咨询

问题描述

我基于Vue 3.4+TypeScript开发了一个Tile组件,初始代码里item属性类型为any。为实现组件通用复用,我将其改为泛型类型T,修改后出现类型错误:Property 'key' does not exist on type 'T'.ts-plugin(2339)。

给泛型添加约束T extends { key: any }后,item.key的类型错误解决了,但Storybook的故事代码又报类型不兼容:

Type '<T extends { key: any; }>(__VLS_props: { "onClick-infobutton"?:
(() => any) | undefined; "onUpdate:modelValue"?: ((modelValue: T[]) =>
any) | undefined; modelValue: T[]; name: string; label?: string |
undefined; ... 10 more ...; item: T; } & VNodeProps &
AllowedComponentProps & ComponentCustomProps, __VLS_ctx?: { .....' has
no properties in common with type 'Omit<ConcreteComponent,
"props">'.ts(2559) ...

需求:保持组件通用可复用(可传入Appliance这类任意带key的对象),同时解决Storybook的报错问题。

解决方案

1. 调整组件泛型定义方式

Vue 3.4的<script setup>泛型语法在Storybook中可能存在类型推导问题,可换用defineProps的泛型约束来实现,避免使用<script generic>属性:

修改组件的<script setup>部分:

<script lang="ts" setup>
import ErrorMessage from '@/components/display/ErrorMessage.vue'
import FormControl from '@/components/layout/FormControl.vue'
import { computed } from 'vue'
import useInput from '@/composable/common/use-input'
import TileCardContent from '@/components/common/tile/TileCardContent.vue'

// 定义带key的基础类型
type WithKey = { key: string | number }

// 用泛型约束props,确保item包含key属性
const props = defineProps<{
  name: string
  label?: string
  inlineContent?: boolean
  item: WithKey & Record<string, any> // 兼容任意带key的扩展对象
}>()

// 定义model值为带key的对象数组
const modelValue = defineModel<WithKey[]>({ required: true })
const { errors, value, isValid, errorMessagesId } = useInput<WithKey[]>(props.name)

const checked = computed({
  get() {
    return value.value ? value.value.some(item => item.key === props.item.key) : false;
  },
  set(checked: boolean) {
    if (!value.value) {
      value.value = []; 
    }
    if (checked) {
      value.value.push(props.item);
    } else {
      value.value = value.value.filter(item => item.key !== props.item.key);
    }
  }
});

function toggleModelValue() {
  checked.value = !checked.value
}
</script>

2. 显式声明组件泛型类型(保留泛型的方案)

如果要严格保留泛型定义,可在组件文件中添加额外的类型声明,帮助Storybook识别泛型组件:

在组件文件末尾添加独立的<script>标签:

<script lang="ts">
import type { DefineComponent } from 'vue'

// 声明泛型组件类型
export type TileProps<T extends { key: any }> = {
  name: string
  label?: string
  inlineContent?: boolean
  item: T
}

type TileComponent<T extends { key: any }> = DefineComponent<TileProps<T>>

// 导出组件类型供Storybook使用
export default {} as TileComponent<any>
</script>

然后在Storybook代码中显式指定泛型参数:

import type { Meta, StoryObj } from '@storybook/vue3'
import Tile, type { TileComponent } from './Tile.vue'
import * as yup from 'yup'
import { useField } from 'vee-validate'
import { ref, watch, onMounted } from 'vue'
import { withCodeSnippet } from '../../../../.storybook/decorator'

type Appliance = {
  key: string
  coefficient: number
}

// 显式指定Tile的泛型为Appliance
const meta: Meta<TileComponent<Appliance>> = {
  component: Tile,
}

export default meta
type Story = StoryObj<TileComponent<Appliance>>

export const Default: Story = {
  name: 'With validation',
  render: (args) => ({
    components: { Tile },
    setup() {
      const appliances: Appliance[] = [
        { key: 'appliance1', coefficient: 10 },
        { key: 'appliance2', coefficient: 20 },
        { key: 'appliance3', coefficient: 30 },
        { key: 'appliance4', coefficient: 40 },
        { key: 'appliance5', coefficient: 50 }
      ]

      const modelValue = ref<Appliance[]>([])

      const { errors, value, validate } = useField<Appliance[]>(
        'appliances',
        yup
          .array()
          .of(
            yup.object().shape({
              key: yup.string().required(),
              coefficient: yup.number().required()
            })
          )
          .required()
          .min(1)
      )
      watch(
        modelValue,
        (newValue) => {
          value.value = newValue
          validate()
        },
        { deep: true }
      )
      onMounted(async () => {
        await validate()
      })

      return {
        appliances,
        modelValue,
        errors,
        value
      }
    },
    template: `
    <div class="ods__d-grid ods__gap ods__g-cols-2">      
      <div v-for="appliance in appliances" :key="appliance.key">
        <Tile 
          v-model="modelValue" 
          :item="appliance" 
          :label="appliance.key" 
          :name="appliance.key"
        />
      </div>
    </div> 
    <p v-if="errors">Errors: {{ errors }}</p> 
    <p>Selected Appliances: {{ modelValue }}</p>
  `
  })
}

问题根源

Storybook对Vue 3 <script setup>的generic属性类型推导支持不完善,无法自动识别泛型组件的参数结构。通过调整泛型定义方式或显式声明组件类型,可让Storybook正确解析组件的类型约束,解决类型不兼容报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:42:01