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

在Storybook中利用Pinia Store切换组件主题的实现问询

问题描述

我正在开发一款支持多主题的应用,主题ID存储在Pinia Store(app.store.ts)中,组件通过getter获取该ID。目前我为组件编写Storybook故事供设计师查看,但组件依赖Store的getter获取主题ID,我希望在同一页面展示所有主题样式,不清楚能否在每个故事前更新Store——比如让AuthInputTheme1使用theme_id=1,AuthInputTheme2使用theme_id=2。请问这是否可行,还是应该将主题ID作为props传入?

代码示例

storybook/preview.ts

const pinia = createPinia();

setup((app: App): void => {
  app.use(FloatingVue)
  app.use(pinia);
});

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

Storybook文件

import AuthInput from './AuthInput.vue';

const meta = {
  title: 'Molecule/AuthInput',
  component: AuthInput,
  tags: ['autodocs'],
  args: {},
} satisfies Meta<typeof AuthInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const AuthInputTheme1: Story = {
  args: {},
};

// update store here?

export const AuthInputTheme2: Story = {
  args: {},
};

app.store.ts

export const useAppStore = defineStore('appStore', {
    state: (): IntakeFormStore => ({
        theme_id: 1,
    })
};

解决方案

两种方案都可行,各有适用场景,具体如下:

方案一:在Storybook故事中更新Pinia Store

完全可以通过Storybook的play或render函数,在每个故事初始化时更新Store的主题ID,实现不同故事对应不同主题的效果。

代码修改示例

在Story文件中引入Store并更新状态:

import AuthInput from './AuthInput.vue';
import { useAppStore } from '@/stores/app.store'; // 替换为你的Store实际路径

const meta = {
  title: 'Molecule/AuthInput',
  component: AuthInput,
  tags: ['autodocs'],
  args: {},
} satisfies Meta<typeof AuthInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const AuthInputTheme1: Story = {
  args: {},
  play: async () => {
    const appStore = useAppStore();
    appStore.theme_id = 1; // 直接更新Store状态
  },
};

export const AuthInputTheme2: Story = {
  args: {},
  play: async () => {
    const appStore = useAppStore();
    appStore.theme_id = 2;
  },
};

如果需要在渲染前就完成状态更新,也可以用render函数:

export const AuthInputTheme2: Story = {
  args: {},
  render: () => {
    const appStore = useAppStore();
    appStore.theme_id = 2;
    return {
      components: { AuthInput },
      template: '<AuthInput />',
    };
  },
};

注意:若Storybook开启了状态共享,建议在每个故事执行前重置Store状态,避免不同故事互相干扰。

方案二:将主题ID作为Props传入组件

这是更符合组件设计原则的方案——让组件尽量无状态,依赖外部传入的props决定样式,而非直接绑定全局Store。这种方式能提升组件复用性,测试和Storybook展示也更灵活。

代码修改示例

  1. 先修改AuthInput.vue,添加themeId props:
<script setup>
import { defineProps, computed } from 'vue';
import { useAppStore } from '@/stores/app.store';

const props = defineProps({
  themeId: {
    type: Number,
    default: () => {
      // 保留原有逻辑:未传入props时从Store获取主题ID
      const appStore = useAppStore();
      return appStore.theme_id;
    },
  },
});

// 替换原有getter,改用props.themeId
const currentTheme = computed(() => props.themeId);
</script>
  1. 再在Storybook中直接传入不同主题ID:
import AuthInput from './AuthInput.vue';

const meta = {
  title: 'Molecule/AuthInput',
  component: AuthInput,
  tags: ['autodocs'],
  args: {
    themeId: 1, // 默认主题ID
  },
  argTypes: {
    themeId: {
      control: 'number',
      description: '主题ID',
    },
  },
} satisfies Meta<typeof AuthInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const AuthInputTheme1: Story = {
  args: {
    themeId: 1,
  },
};

export const AuthInputTheme2: Story = {
  args: {
    themeId: 2,
  },
};

方案对比

  • 方案一:无需修改组件代码,快速实现多主题展示,但组件与全局Store耦合度高,复用性弱。适合临时快速展示,或组件确实强依赖全局状态的场景。
  • 方案二:组件更通用,耦合度低,不仅Storybook展示方便,后续组件复用也更灵活。推荐作为长期方案,符合Vue组件设计理念。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:44:51