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

如何为Storybook中自定义渲染的Vue 3故事生成正确源码?

Storybook Vue3自定义渲染故事的源码展示问题

我在Storybook中为Vue3组件编写自定义渲染的故事时,渲染结果正确,但生成的展示源码不符合预期,具体示例如下:

示例1

故事代码

export const Story1 = {
  args: { label: "ButtonLabel" },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<MyButton>{{args.label}}</MyButton>`,
  }),
};

预期源码输出

<MyButton>ButtonLabel</MyButton>

实际源码输出

<MyButton />

示例2

故事代码

export const Story2 = {
  args: { label: "ButtonLabel" },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<div><MyButton v-for="count in 3" :key="count">{{args.label}}</MyButton></div>`,
  }),
};

预期源码输出

<div>
 <MyButton>ButtonLabel</MyButton>
 <MyButton>ButtonLabel</MyButton>
 <MyButton>ButtonLabel</MyButton>
</div>

实际源码输出

<MyButton v-for="k in 3" key="k" />

我知道可以通过parameters.docs.source.code或parameters.docs.source.transform自定义源码输出,但不知道具体怎么实现上述预期效果,有没有合适的库可以满足需求?


解决方案

方法1:直接指定parameters.docs.source.code

如果每个故事的预期源码是固定的,直接在故事的parameters里硬编码目标HTML即可:

针对示例1:

export const Story1 = {
  args: { label: "ButtonLabel" },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<MyButton>{{args.label}}</MyButton>`,
  }),
  parameters: {
    docs: {
      source: {
        code: `<MyButton>ButtonLabel</MyButton>`
      }
    }
  }
};

针对示例2:

export const Story2 = {
  args: { label: "ButtonLabel" },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<div><MyButton v-for="count in 3" :key="count">{{args.label}}</MyButton></div>`,
  }),
  parameters: {
    docs: {
      source: {
        code: `<div>
 <MyButton>ButtonLabel</MyButton>
 <MyButton>ButtonLabel</MyButton>
 <MyButton>ButtonLabel</MyButton>
</div>`
      }
    }
  }
};

方法2:使用parameters.docs.source.transform动态生成

如果需要根据args动态生成源码,用transform函数处理——它接收当前故事的上下文(包含args),返回最终要展示的源码字符串:

示例1的动态处理:

export const Story1 = {
  args: { label: "ButtonLabel" },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<MyButton>{{args.label}}</MyButton>`,
  }),
  parameters: {
    docs: {
      source: {
        transform: (story) => {
          const { label } = story.args;
          return `<MyButton>${label}</MyButton>`;
        }
      }
    }
  }
};

示例2的动态处理:

export const Story2 = {
  args: { label: "ButtonLabel", count: 3 }, // 将count设为args,方便动态修改
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<div><MyButton v-for="num in args.count" :key="num">{{args.label}}</MyButton></div>`,
  }),
  parameters: {
    docs: {
      source: {
        transform: (story) => {
          const { label, count } = story.args;
          const buttons = Array(count).fill(` <MyButton>${label}</MyButton>`).join('\n');
          return `<div>\n${buttons}\n</div>`;
        }
      }
    }
  }
};

推荐工具库

如果需要复杂的模板编译、格式化逻辑,可使用以下轻量库:

  • html-tags:自动补全HTML标签、格式化缩进,让输出源码更规整
  • mustache:简单模板引擎,方便根据args填充模板内容

比如结合mustache实现动态源码生成:

import mustache from 'mustache';

// 预定义模板
const story2Template = `<div>
{{#count}}
 <MyButton>{{label}}</MyButton>
{{/count}}
</div>`;

export const Story2 = {
  args: { label: "ButtonLabel", count: 3 },
  render: (args) => ({
    components: { MyButton },
    setup() {
        return {args}
    },
    template: `<div><MyButton v-for="num in args.count" :key="num">{{args.label}}</MyButton></div>`,
  }),
  parameters: {
    docs: {
      source: {
        transform: (story) => {
          // 生成对应长度的数组供mustache遍历
          const data = {
            ...story.args,
            count: Array(story.args.count).fill(null)
          };
          return mustache.render(story2Template, data);
        }
      }
    }
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:38:22