如何在Storybook中渲染Vue 3动态插槽内容?
问题:Storybook中Vue组件插槽内容渲染异常,显示内部代码
我用以下Storybook代码测试Vue按钮组件,期望渲染出带有“Primary Button”文本的Primary按钮,但实际按钮内显示的是一段Vue内部渲染函数代码:
(...args) => { // If a user calls a compiled slot inside a template expression (#1745), it // can mess up block tracking, so by default we disable block tracking and // force bail out when invoking a compiled slot (indicated by the ._d flag). // This isn't necessary if rendering a compiled `<slot>`, so we flip the // ._d flag off when invoking the wrapped fn inside `renderSlot`. if (renderFnWithContext._d) { setBlockTracking(-1); } const prevInstance = setCurrentRenderingInstance(ctx); const res = fn(...args); setCurrentRenderingInstance(prevInstance); if (renderFnWithContext._d) { setBlockTracking(1); } if (true) { devtoolsComponentUpdated(ctx); } return res; }
我的Storybook代码如下:
// ./components/button/Button.stories.js import AtxButton from './Button'; import ButtonTypes from './Button.types'; export default { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading * to learn how to generate automatic titles */ title: 'Button', component: AtxButton, argTypes: { default: { description: 'The default Vue slot', control: 'text' } } }; //👇 We create a “template” of how args map to rendering const Template = (args, { argTypes }) => ({ props: Object.keys(argTypes), components: { AtxButton }, setup() { //👇 The args will now be passed down to the template return { args }; }, template: ` <AtxButton v-bind="args">{{ args.default }} </AtxButton> ` }); //👇 Each story then reuses that template export const Primary = Template.bind({}); Primary.args = { intensity: ButtonTypes.Intensity.Primary, default: 'Primary Button' }; export const Secondary = Template.bind({}); Secondary.args = { intensity: ButtonTypes.Intensity.Secondary, default: 'Secondary Button' };
解决方法
问题出在你把Vue的默认插槽当成普通prop来处理了。在Vue中,default是默认插槽的标识,不是组件的prop,直接在模板里写{{ args.default }}会把插槽对应的渲染函数当成普通值输出,所以才会显示那段内部代码。
下面是几种正确的处理方式:
方式一:使用Vue原生插槽语法
修改Template模板,用<slot />来接收插槽内容,Storybook会自动把args里的default内容传入插槽:
const Template = (args, { argTypes }) => ({ // 过滤掉default,因为它是插槽不是prop props: Object.keys(argTypes).filter(key => key !== 'default'), components: { AtxButton }, setup() { return { args }; }, template: ` <AtxButton v-bind="args"> <slot /> </AtxButton> ` });
方式二:自定义参数名替代default
如果想在Controls面板里动态修改插槽文本,可以把default换成自定义参数(比如slotText),避免和Vue插槽的默认标识冲突:
export default { title: 'Button', component: AtxButton, argTypes: { slotText: { description: '按钮文本(默认插槽内容)', control: 'text' } } }; const Template = (args) => ({ components: { AtxButton }, setup() { return { args }; }, template: ` <AtxButton v-bind="args"> {{ args.slotText }} </AtxButton> ` }); export const Primary = Template.bind({}); Primary.args = { intensity: ButtonTypes.Intensity.Primary, slotText: 'Primary Button' }; export const Secondary = Template.bind({}); Secondary.args = { intensity: ButtonTypes.Intensity.Secondary, slotText: 'Secondary Button' };
方式三:直接在模板中指定插槽内容
如果不需要动态修改,也可以直接在Template的模板里写死插槽内容:
const Template = (args) => ({ components: { AtxButton }, setup() { return { args }; }, template: ` <AtxButton v-bind="args"> Primary Button </AtxButton> ` });
以上任意一种方式都能让按钮正确显示预期的文本内容,不会再输出内部渲染代码。
内容的提问来源于stack exchange,提问作者squirrelwhisperer
相关产品推荐
相关产品推荐

