在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展示也更灵活。
代码修改示例
- 先修改
AuthInput.vue,添加themeIdprops:
<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>
- 再在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
相关产品推荐
相关产品推荐

