文档
Storybook Docs

工具栏和全局变量

观看视频教程

Storybook 附带工具栏插件,用于控制 story 渲染的 viewportbackground。 你还可以创建工具栏项来控制特殊的“全局变量”。 然后,你可以读取全局变量值以创建 decorators 来控制 story 渲染。

Toolbars and globals

全局变量

Storybook 中的全局变量代表 story 渲染的“全局”(即非 story 特定的)输入。 由于它们不是 story 特有的,因此它们不会在 args 参数中传递给 story 函数(尽管它们可以作为 context.globals 访问)。 相反,它们通常用于 decorators,decorators 应用于所有 story。

当全局变量更改时,story 会重新渲染,并且 decorators 会使用新值重新运行。 更改全局变量的最简单方法是为它们创建工具栏项。

全局类型和工具栏注释

Storybook 具有用于配置工具栏菜单的简单声明式语法。 在你的 .storybook/preview.js|ts 中,你可以通过创建带有 toolbar 注释的 globalTypes 来添加自己的工具栏

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., react, vue3)
import { Preview } from '@storybook/your-framework';
 
const preview: Preview = {
  globalTypes: {
    theme: {
      description: 'Global theme for components',
      toolbar: {
        // The label to show for this toolbar item
        title: 'Theme',
        icon: 'circlehollow',
        // Array of plain string values or MenuItem shape (see below)
        items: ['light', 'dark'],
        // Change title based on selected value
        dynamicTitle: true,
      },
    },
  },
  initialGlobals: {
    theme: 'light',
  },
};
 
export default preview;

由于全局变量是全局的,因此你只能.storybook/preview.js|ts 中设置 globalTypesinitialGlobals

当你启动 Storybook 时,你的工具栏应该有一个新的下拉菜单,其中包含 lightdark 选项。

创建 decorator

我们已经实现了一个 global。 让我们把它连接起来! 我们可以在 decorator 中使用 context.globals.theme 值来使用我们新的 theme 全局变量。

例如,假设你正在使用 styled-components。 你可以向你的 .storybook/preview.js|ts 配置添加一个 theme provider decorator

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., solid, qwik)
import { Preview } from '@storybook/your-framework';
 
import { MyThemes } from '../my-theme-folder/my-theme-file';
 
const preview: Preview = {
  decorators: [
    (story, context) => {
      const selectedTheme = context.globals.theme || 'light';
      const theme = MyThemes[selectedTheme];
      // Your theme provider and other context providers goes in the return statement
      return;
    },
  ],
};
 
export default preview;

在 story 上设置全局变量

在 storybook 8.3+ 版本中,可以使用在 story 或组件上设置全局变量的功能。 某些插件,例如 backgroundsviewport,在启用 feature flag 时已更新为使用 globals API。

当全局变量值通过 Storybook 中的工具栏菜单更改时,该值会在你在 story 之间导航时继续使用。 但有时 story 需要特定值才能正确渲染,例如,当针对特定环境进行测试时。

为了确保 story 始终使用特定的全局变量值,而不管在工具栏中选择了什么,你可以在 story 或组件上设置 globals 注释。 这会覆盖这些 story 的全局变量值,并在查看 story 时禁用该全局变量的工具栏菜单。

Button.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  component: Button,
  globals: {
    // 👇 Set background value for all component stories
    backgrounds: { value: 'gray', grid: false },
  },
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
export const OnDark: Story = {
  globals: {
    // 👇 Override background value for this story
    backgrounds: { value: 'dark' },
  },
};

在上面的示例中,Storybook 将强制所有 Button stories 使用灰色背景颜色,但 OnDark story 除外,它将使用深色背景。 对于所有 Button stories,工具栏菜单将为 backgrounds 全局变量禁用,并带有工具提示,说明全局变量是在 story 级别设置的。

配置 story 的 globals 注释以覆盖项目级全局设置非常有用,但应谨慎使用。 未在 story 级别定义的全局变量可以在 Storybook 的 UI 中以交互方式选择,允许用户探索每个现有的值组合(例如,全局变量值、args)。 在 story 级别设置它们将禁用该控件,从而阻止用户探索可用选项。

高级用法

到目前为止,我们已经在 Storybook 内部创建并使用了全局变量。

现在,让我们看一个更复杂的例子。 假设我们要为国际化实现一个新的全局变量,名为 locale,它在工具栏的右侧显示一个标志。

在你的 .storybook/preview.js|ts 中,添加以下内容

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., react, vue3)
import { Preview } from '@storybook/your-framework';
 
const preview: Preview = {
  globalTypes: {
    locale: {
      description: 'Internationalization locale',
      toolbar: {
        icon: 'globe',
        items: [
          { value: 'en', right: '🇺🇸', title: 'English' },
          { value: 'fr', right: '🇫🇷', title: 'Français' },
          { value: 'es', right: '🇪🇸', title: 'Español' },
          { value: 'zh', right: '🇨🇳', title: '中文' },
          { value: 'kr', right: '🇰🇷', title: '한국어' },
        ],
      },
    },
  },
  initialGlobals: {
    locale: 'en',
  },
};
 
export default preview;

示例中使用的 icon 元素从 @storybook/components 包加载图标。 有关你可以使用的可用图标列表,请参阅此处

要使用工具栏,你必须安装 @storybook/addon-toolbars 插件,该插件默认包含在 @storybook/addon-essentials 中。

添加配置元素 right 将在工具栏菜单的右侧显示文本,一旦你将其连接到 decorator。

以下是可用配置选项的列表。

MenuItem类型描述必需
value字符串在全局变量中设置的菜单的字符串值
title字符串标题的主文本
right字符串显示在菜单右侧的字符串
icon字符串如果选择此项,则在工具栏中显示的图标

从 story 中使用全局变量

我们建议从 decorator 中使用全局变量,并为所有 story 定义全局设置。

但我们意识到,有时在每个 story 的基础上使用工具栏选项更有益。

使用上面的示例,你可以修改任何 story 以从 story 上下文中检索 Locale global

MyComponent.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
const getCaptionForLocale = (locale) => {
  switch (locale) {
    case 'es':
      return 'Hola!';
    case 'fr':
      return 'Bonjour!';
    case 'kr':
      return '안녕하세요!';
    case 'zh':
      return '你好!';
    default:
      return 'Hello!';
  }
};
 
export const StoryWithLocale = {
  render: (args, { globals: { locale } }) => {
    const caption = getCaptionForLocale(locale);
    return <p>{caption}</p>;
  },
};

从插件中使用全局变量

如果你正在开发 Storybook 插件并且需要检索全局变量,你可以这样做。 @storybook/manager-api 包为此场景提供了一个 Hook。 你可以使用 useGlobals() Hook 来检索你想要的任何全局变量。

使用上面的 ThemeProvider 示例,你可以扩展它以在面板内显示哪个主题处于活动状态,如下所示

your-addon-register-file.js
import React from 'react';
 
import { useGlobals } from '@storybook/manager-api';
 
import { AddonPanel, Placeholder, Separator, Source, Spaced, Title } from '@storybook/components';
 
import { MyThemes } from '../my-theme-folder/my-theme-file';
 
// Function to obtain the intended theme
const getTheme = (themeName) => {
  return MyThemes[themeName];
};
 
const ThemePanel = (props) => {
  const [{ theme: themeName }] = useGlobals();
 
  const selectedTheme = getTheme(themeName);
 
  return (
    <AddonPanel {...props}>
      {selectedTheme ? (
        <Spaced row={3} outer={1}>
          <Title>{selectedTheme.name}</Title>
          <p>The full theme object</p>
          <Source
            code={JSON.stringify(selectedTheme, null, 2)}
            language="js"
            copyable
            padded
            showLineNumbers
          />
        </Spaced>
      ) : (
        <Placeholder>No theme selected</Placeholder>
      )}
    </AddonPanel>
  );
};

从插件中更新全局变量

如果你正在开发一个需要更新全局变量并刷新 UI 的 Storybook 插件,你可以这样做。正如之前提到的,@storybook/manager-api 包为此场景提供了必要的 Hook。你可以使用 updateGlobals 函数来更新任何你需要的全局变量。

例如,如果你正在开发一个工具栏插件,并且你希望在用户点击按钮后刷新 UI 并更新全局变量

your-addon-register-file.js
import React, { useCallback } from 'react';
 
import { FORCE_RE_RENDER } from '@storybook/core-events';
import { useGlobals } from '@storybook/manager-api';
 
import { IconButton } from '@storybook/components';
import { OutlineIcon } from '@storybook/icons';
 
import { addons } from '@storybook/preview-api';
 
const ExampleToolbar = () => {
  const [globals, updateGlobals] = useGlobals();
 
  const isActive = globals['my-param-key'] || false;
 
  // Function that will update the global value and trigger a UI refresh.
  const refreshAndUpdateGlobal = () => {
    // Updates Storybook global value
    updateGlobals({
      ['my-param-key']: !isActive,
    }),
      // Invokes Storybook's addon API method (with the FORCE_RE_RENDER) event to trigger a UI refresh
      addons.getChannel().emit(FORCE_RE_RENDER);
  };
 
  const toggleOutline = useCallback(() => refreshAndUpdateGlobal(), [isActive]);
 
  return (
    <IconButton
      key="Example"
      active={isActive}
      title="Show a Storybook toolbar"
      onClick={toggleOutline}
    >
      <OutlineIcon />
    </IconButton>
  );
};