storybook [Bug]: Vue3 自动推断的 argTypes 不起作用

vjhs03f7  于 5个月前  发布在  其他
关注(0)|答案(4)|浏览(52)

描述问题

我正在使用最新版本的storybook,版本号为7.0.20。我使用的是vite+vue预设。我有一个组件,并且为它编写了故事,但是自动推断的argTypes效果不佳:

虽然有事件,但我在Actions面板中看不到任何日志。为了查看日志,我需要手动编写:

const meta: Meta<typeof Editor> = {
  component: Component,
  argTypes: {
    onReady: {},
  },
}

现在,有了日志,但是Controls面板看起来不太正常:

我认为,自动推断的argTypes应该是这样的:

{
  ready: {
    table: {
      category: 'events'
    }
  }
}

也许我们可以改进它,让parameters.actions.argTypesRegex: "^on[A-Z].*"能够自动捕获所有事件。

重现方法

  • 无响应*

系统信息

  • 无响应*

其他上下文信息

  • 无响应*
kcugc4gi

kcugc4gi1#

我为SB文档模块创建了一个自定义的extractArgTypes,以改进我项目中的许多类型,特别是与TS相关的。其中一个改进是将事件设置到属性on{EventName}上。

// docsParameters.ts
import {
  DocgenPropType,
  hasDocgen,
  extractComponentProps,
  convert,
} from '@storybook/docs-tools'
import type { InputType, SBType } from '@storybook/types'
import type { Prop } from 'vue'

type DocgenExtendedPropType = DocgenPropType & {
  elements?: DocgenExtendedPropType[]
  names?: [string]
}

const SECTIONS = ['props', 'events', 'slots', 'methods'] as const

function toEventName(name: string) {
  return `on${name.charAt(0).toUpperCase()}${name.slice(1)}`
}

function getUnionTypes(
  docgenType: DocgenExtendedPropType | undefined,
): string[] {
  if (docgenType?.name === 'Array')
    return [`${docgenType.elements?.map((v) => v.name).join('|')}[]`]
  return (
    docgenType?.elements?.flatMap((v) =>
      v.elements ? getUnionTypes(v) : [v.name],
    ) ?? []
  )
}

function isStringType(unionTypes: string[]) {
  return (
    unionTypes.length > 0 &&
    unionTypes.every(
      (t) => t === 'string' || (t.startsWith('"') && t.endsWith('"')),
    )
  )
}

/**
* Same as Storybook `extractArgTypes` with the following changes:
* - Remove control from events and methods.
* - Add `on` prefix to events, so that `actions: { argTypesRegex: '^on[A-Z].*' }` can be used.
* - Get event types from TS `defineEmits`. docgen info has it in `names` prop.
* - Set types on `update:[prop]` events based on [prop] type
* - Add all props not defined by docgen info in a group called "other props"
* - Expand union type and use radio/select control when values are strings
* - Expand array types
* @see https://github.com/storybookjs/storybook/blob/d5ca2f42838c9f5a3e556a5e819e58f0deff522e/code/renderers/vue3/src/docs/extractArgTypes.ts
*/
export function extractArgTypes(component: any) {
  if (!hasDocgen(component)) return null
  const results: Record<string, InputType> = {}
  SECTIONS.forEach((section) => {
    extractComponentProps(component, section).forEach(
      ({ propDef, docgenInfo, jsDocTags }) => {
        const { name, type, description, defaultValue, required } = propDef,
          sbType = section === 'props' ? convert(docgenInfo) : { name: 'void' }
        const docgenType: DocgenExtendedPropType | undefined = docgenInfo.type
        const unionTypes = getUnionTypes(docgenType)
        const summary =
          unionTypes.join(' | ') || type?.summary || docgenType?.names?.[0]
        const inputType: InputType = {
          name,
          description,
          type: isStringType(unionTypes)
            ? { required, name: 'string' }
            : { required, ...sbType },
          table: {
            type: {
              ...type,
              summary,
            },
            jsDocTags,
            defaultValue,
            category: section,
          },
        }
        if (section === 'events') {
          inputType.control = null
          inputType.type = 'function'
          const propName = /^update:(.+)$/.exec(name)?.[1]
          const result = results[propName!]
          if (result) {
            inputType.table.type = result.table.type
          }
        } else if (isStringType(unionTypes)) {
          const options: (string | undefined)[] = unionTypes
            .map((t) => /^"(.+)"$/.exec(t)?.[1])
            .filter(Boolean)
          if (!required) options.unshift(undefined)
          inputType.options = options
          inputType.control = {
            type: options.length <= 5 ? 'radio' : 'select',
            labels: { undefined: '𝘶𝘯𝘥𝘦𝘧𝘪𝘯𝘦𝘥' },
          }
        }
        const argName = section === 'events' ? toEventName(name) : name
        results[argName] = inputType
      },
    )
  })
  Object.entries(component.props as Prop<any>[]).forEach(([name, prop]) => {
    if (name in results) return
    const {
      default: defaultValue,
      required,
      type,
    } = typeof prop === 'function' || Array.isArray(prop)
      ? ({ type: prop } satisfies Prop<any>)
      : prop
    const sbType: SBType =
      type === String
        ? { name: 'string' }
        : type === Number
        ? { name: 'number' }
        : type === Boolean
        ? { name: 'boolean' }
        : type === Function
        ? { name: 'function' }
        : type === Object
        ? { name: 'object', value: {} }
        : type === Array
        ? {
            name: 'array',
            value: { name: 'other', value: 'Unknown' },
          }
        : { name: 'other', value: 'Unknown' }
    results[name] = {
      name,
      type: { required, ...sbType },
      table: {
        type: {
          summary: sbType.name === 'other' ? 'unknown' : sbType.name,
        },
        defaultValue,
        category: 'other props',
      },
    }
  })
  return results
}
// preview.ts
import { extractArgTypes } from './docsParameters'

export default {
  parameters: {
    docs: { extractArgTypes }
  }
}

如果#22285被合并,这里的许多更改都将得到改善/提高,但我认为这个问题不会得到解决。检查extractArgTypes函数:
storybook/code/renderers/vue/src/docs/extractArgTypes.ts
d07bfc5的第63行至第75行
| | argTypes[name]={ |
| | name, |
| | description: descriptions.replace('undefined',''), |
| | defaultValue: {summary: defaultSummary}, |
| | type: { required, ...sbType}asSBType, |
| | table: { |
| | type: {summary: definedTypes}, |
| | jsDocTags: tags, |
| | defaultValue: {summary: defaultSummary}, |
| | category: section, |
| | }, |
| | control: {disable: !['props','slots'].includes(section)}, |
| | }; |
它仍然有相同的名称。
如果你想创建PR来修复这个问题,更改将在以下链接中:https://github.com/storybookjs/storybook/blob/next/code/renderers/vue/src/docs/extractArgTypes.ts#L74

xjreopfe

xjreopfe2#

当我在使用Storybook v7时,我没有感到惊讶,但在v8中,尽管Vue支持得到了改进,这个问题仍然存在。我困惑于它最初是如何被设计的。
重述问题:

// Component.vue

<script setup lang="ts">
defineEmits(['someEvent']);
</script>
// Component.stories.ts

import Component from 'Component.vue'
import type { Meta, StoryObj } from '@storybook/vue3'

const meta = {
  component: Component
} satisfies Meta<typeof Component>

然后:

  1. Storybook将在"Events"面板下渲染 someEvent

  1. 当事件触发时,Storybook将不会记录一个动作
    现在,如果我们尝试手动声明这个事件:
// Component.stories.ts

import Component from 'Component.vue'
import type { Meta, StoryObj } from '@storybook/vue3'
+++ import { fn } from '@storybook/test';
+++ import { action } from '@storybook/addon-actions';

const meta = {
  component: Component,
  +++ args: {
  +++   onSomeEvent: fn(), // This works
  +++   onSomeEvent: action('onSomeEvent'), // This works
  +++ },
  +++ argTypes: {
  +++   onSomeEvent: {
  +++     action: 'onSomeEvent', // This also works
  +++   }
  +++ }
} satisfies Meta<typeof Component>
  1. 每一个选项, fn() , action ,或者在 argTypes 下声明,都能成功触发一个动作(从阅读文档中我不清楚哪个版本更优)
  2. 然而 ,如果我们尝试定义一个 someEvent 属性,TypeScript会报错;只有 onSomeEventsatisfies Meta<typeof Component>
    所以,似乎 onSomeEvent 被正确推断出来了,因此具有类型安全性。如果是这样的话,那么:
  3. 为什么 someEvent 而不是 onSomeEvent 没有被渲染到"Events"面板?
  4. 为什么在这里 argTypesRegex: '^on[A-Z].*' 没有效果?(见编辑后的评论)
    如果这个问题还没有被打开的话,我本以为我犯了一个非常低级的错误。

编辑: 在重新阅读原始的工单后,我意识到我描述的问题并没有改变。 argTypesRegex 确实按预期工作。如果我按照原始工单中描述的方式定义 argTypes: { onSomeEvent: { } } (如注解所提到的),Storybook会自动分配一个动作。

那么,我现在提出的唯一新问题是,类型安全期望事件前缀为 on ,而推断则是发现并分配没有这个前缀的事件。一旦推断修复了,这就不再是问题了。

gijlo24d

gijlo24d3#

让我尝试澄清一些困惑。TLDR,你是对的 :)

Storybook 作为一个 Vue h 调用,所以对于以下组件:

<script setup lang="ts">
const props = defineProps({
label: String,
year: Number,
});
defineEmits(['someEvent']);
</script>
<template>
  <div @click="$emit('someEvent')">
    <slot :text="`Hello ${props.label} from the slot`" :year="props.year"></slot>
  </div>
</template>
const meta = {
  component: SomeComponent,
}

const Default = {
  args: {
    label: 'Some text',
    year: 2022,
    default: ({ text, year }) => `${text}, ${year}`,
    onSomeEvent: fn(),
  },
}

在底层只是调用了 h 函数:

h(meta.component, Default.args, filterSlotArgs(Default.args))

将事件前缀为 on 的习惯来自 Vue 本身,参见:

所以我认为你是对的,我们需要确保我们的 docgen 分析在推断出的 argTypes 是事件时,用 on 进行前缀。WDYT @larsrickert@chakAs3 ?

ars1skjm

ars1skjm4#

我认为你是对的,我们需要确保我们的docgen分析在事件的情况下为推断出的argTypes添加前缀on。WDYT @larsrickert@chakAs3 ?
我不确定这是否正确,因为那时事件会显示在表格中的on前缀中,这是不正确的,因为在使用vue组件时,没有on前缀:

<MyComponent @some-event />

所以使用@on-some-event实际上是无效的。因此,仅在Storybook内部需要(如果需要的话)on前缀。
但是,我同意这很令人困惑,因为正确的事件名称是“someEvent”,但它需要定义为onSomeEvent。我们在当前项目中也遇到了同样的问题,我们创建了自己的函数来定义事件,该函数将定义onSomeEvent作为操作,但将其隐藏在表格中,只向用户显示someEvent
我想如果事件能自动记录/定义为操作,那么主要问题就会解决,这样就不需要定义argType了,对吗?

相关问题