首页
/ 深入掌握 Storybook Addon API:用 selectInCurrentKind 在组件内快速切换 Story

深入掌握 Storybook Addon API:用 selectInCurrentKind 在组件内快速切换 Story

2026-09-08 19:42:01作者:殷蕙予

selectInCurrentKind 是 Storybook Addon API 中一个轻量而实用的导航方法,它允许开发者在已注册的 addon 中、仅凭一个 story 名称即可把当前界面切换到“当前组件(kind)”内的对应 story,无需关心组件的完整标题或 storyId。本文以 docs/_snippets/storybook-addons-api-selectincurrentkind.md 为骨架,结合 docs/addons/addons-api.mdx 的 API 文档与 code/core/src/manager-api/modules/stories.ts 的源码实现,完整讲解该 API 的用法、参数语义、底层导航原理及实战场景,读完即可在你的自定义 addon 中熟练使用。

一、selectInCurrentKind 是什么

在 Storybook 的 Addon API 体系中,selectInCurrentKind 属于“Storybook API”层的方法,与 api.selectStory() 同族。官方文档对其定位是:

Similar to the selectStory API method, but it only accepts the story as the only parameter.(与 selectStory 类似,但它只接收 story 名称这一个参数。)

也就是说:

  • selectStory 允许你指定 kind(组件标题) 和可选的 story 名称,可在全局范围内跳转到任意 story;
  • selectInCurrentKind 则省略 kind 参数,只针对当前正在查看的组件,在其内部的 story 之间切换。

该 API 在 addon 的注册回调中通过传入的 api 实例调用,标准用法如下(即关联文档中的原始示例):

addons.register('my-organisation/my-addon', (api) => {
  api.selectInCurrentKind('Default');
});

二、使用前提:在 addon 注册回调中获取 api 实例

要调用 selectInCurrentKind,必须先通过 addons.register() 拿到 Storybook API 实例。官方文档指出,addons.register() 是所有 addon 的入口,它允许你注册 addon 并访问 Storybook API(见 docs/addons/addons-api.mdx):

import { addons } from 'storybook/preview-api';

// Register the addon with a unique name.
addons.register('my-organisation/my-addon', (api) => {});

addons 通过 storybook/preview-api 包导入(参见 docs/_snippets/storybook-addons-api-imports.md)。在 register 回调的 api 参数上即可调用 selectInCurrentKind('StoryName')

如果你的 addon 是带 UI 的组件(如 Panel、Toolbar),在组件内部也可以借助 useStorybookApi 钩子(来自 storybook/manager-api)获取同一套 API 实例,再调用导航方法。这种方式适合把“切换 story”动作绑定到按钮、下拉框等交互控件上。

三、与 selectStory 的对比:参数语义与适用场景

官方文档对 selectStory 的说明是:它接受两个参数——story kind 名称和可选的 story 名称。例如要选中 Button 组件下的 Default story(见 docs/_snippets/storybook-addons-api-selectstory.md):

addons.register('my-organisation/my-addon', (api) => {
  api.selectStory('Button', 'Default');
});

两者对比如下:

API 参数 导航范围 适用场景
selectStory(kind, name?) kind 必填(可用组件标题或 storyId),name 可选 全局 跨组件跳转、跳转到指定 storyId、通过组件标题定位
selectInCurrentKind(name) 仅 story 名称 当前组件(kind)内部 在同一组件不同变体/状态之间快速切换

从源码实现看,selectStory 在只传 name(即 titleOrIdundefined)时,其行为与 selectInCurrentKind 完全一致。在 code/core/src/manager-api/modules/stories.ts 中有明确的注释与实现:

} else if (!titleOrId) {
  // Navigate to a named story/docs within the current component (i.e. "kind")
  // This is a slugified version of the kind, but that's OK, our toId function is idempotent
  gotoStory(hash[toId(kindSlug, name)]);
}

因此可以推断:selectInCurrentKind(name) 本质上就是 selectStory(undefined, name) 的便捷形式,省去了“获取当前组件 kind 再拼接”的样板代码。

四、源码级解析:它是如何在当前组件内完成导航的

要真正用好 selectInCurrentKind,理解其底层导航逻辑很有帮助。下面基于 code/core/src/manager-api/modules/stories.tsselectStory 实现逐步拆解。

4.1 关键概念:kindSlug 与 storyId

在 Storybook 中,一个 story 的 ID 由 kind--story 的形式组成。源码中通过如下代码从当前 storyId 提取组件(kind)部分(stories.ts):

const kindSlug = storyId?.split('--', 2)[0];

selectInCurrentKind('Default') 导航时,会先把当前 story 的 storyId-- 切分,取前半段作为组件标识,再定位到当前组件下名为 Default 的 story。

4.2 通过 toId 生成目标 storyId

定位目标 story 时,源码调用 toId(kindSlug, name) 来生成规范化的 storyId(stories.ts):

gotoStory(hash[toId(kindSlug, name)]);

toId@storybook/csf 提供的工具函数(源码中通过 import { sanitize, toId } from 'storybook/internal/csf' 引入,并在模块中暴露为 storyId 属性,见 stories.ts)。它的特性是幂等(idempotent),即使 kind 传入的是已 slug 化的形式,也能得到相同结果。

4.3 最终导航:更新 URL 与记录状态

找到目标 entry 后,gotoStory 会更新 settings.lastTrackedStoryId 并通过 navigateWithQueryParams 将 URL 切换到 /{viewMode}/{entry.id}stories.ts):

const gotoStory = (entry?: API_HashEntry) => {
  if (entry?.type === 'docs' || entry?.type === 'story') {
    store.setState({ settings: { ...settings, lastTrackedStoryId: entry.id } });
    navigateWithQueryParams(
      `/${entry.type}/${entry.refId ? `${entry.refId}_${entry.id}` : entry.id}${scrollTo ? `#${scrollTo}` : ''}`
    );
    return true;
  }
  return false;
};

由此可以看到,selectInCurrentKind 不仅切换了界面,还会同步更新 URL 与 Storybook 内部状态,因此刷新页面或分享链接时能保持选中的 story。

4.4 其他相关导航 API

  • jumpToStory(direction):在当前组件内按顺序跳转到上一个/下一个 story(stories.ts),与 selectInCurrentKind 一样属于“组件内导航”,区别在于它是相对位置导航而非按名称定位;
  • selectFirstStory():选中第一个 story(stories.ts);
  • 跨 iframe 通信时,SELECT_STORY 事件(定义于 code/core/src/core-events/index.ts)会把 kind/story/storyId 参数转发给 selectStory 完成导航(stories.ts)。

五、实战:在自定义 addon 中实现“变体快捷切换”

假设你在开发一个组件测试辅助 addon,希望让用户在当前组件下快速切换不同的 story 变体(如 DefaultDarkRTL),可以这样实现:

import { addons } from 'storybook/preview-api';

addons.register('my-organisation/variant-switcher', (api) => {
  // 在 Toolbar 或 Panel 的按钮点击事件中调用:
  // 切换到当前组件下的 Dark story
  api.selectInCurrentKind('Dark');
});

如果你在 Panel 组件内部使用 React 钩子,则可以写成:

import { useStorybookApi } from 'storybook/manager-api';

function VariantSwitcher() {
  const api = useStorybookApi();
  return (
    <button onClick={() => api.selectInCurrentKind('Dark')}>
      Switch to Dark variant
    </button>
  );
}

使用建议:

  • story 名称要精确匹配selectInCurrentKind 传入的名称需要与当前组件内 story 的导出名或 name 一致(底层通过 toId 拼接查找),拼写不一致会导致无法定位到目标;
  • 确保当前处于 story/docs 视图:若当前没有任何选中的 story,kindSlug 无法从 storyId 中提取,导航不会生效;
  • getCurrentStoryData() 配合:可以先调用 api.getCurrentStoryData() 读取当前 story 的 kindnameid 等信息(见 docs/addons/addons-api.mdx),再做条件判断或动态拼接操作;
  • docs 页面同样适用gotoStory 同时接受 type === 'docs' 的 entry,因此在 MDX 文档页之间也能用类似方式导航。

六、测试证据:行为已被单元测试锁定

Storybook 仓库在 code/core/src/manager-api/tests/stories.test.ts 中为 selectStory 维护了完整的单元测试(describe('selectStory'),见 stories.test.ts),其中直接覆盖了“在当前组件内按名称导航”的语义:

  • 在当前组件内按给定名称导航(对应 selectInCurrentKind 的语义):
it('allows navigating to a given name (in the current component)', () => {
  const initialState = { path: '/story/a--1', storyId: 'a--1', viewMode: 'story' };
  // ...
  api.setIndex({ v: 5, entries: navigationEntries });
  api.selectStory(undefined, '2');
  expect(navigate).toHaveBeenCalledWith('/story/a--2', undefined);
});
  • 按组件 id 跳转到第一个可见子 story:当过滤后的索引中第一个子 story 被隐藏时,会选择第一个可见的子项(stories.test.ts);
  • 按组件标题(title)跳转api.selectStory('A') 也能正确解析并导航到 /story/a--1stories.test.ts);
  • 记录最后跟踪的 story:导航后 settings.lastTrackedStoryId 会被更新(stories.test.ts)。

这些测试表明:无论通过 selectStory 还是 selectInCurrentKind 触发组件内导航,最终都会统一收敛到 selectStory 的“按名称在当前 kind 内定位”分支,行为可预期、有回归保障。

七、小结

selectInCurrentKind 是 Storybook Addon API 中面向“组件内导航”的高频方法:它只接受一个 story 名称参数,自动基于当前组件完成 storyId 解析、URL 更新与状态记录。理解它与 selectStory 的关系(本质上是 selectStory(undefined, name) 的便捷封装)、底层 kindSlug + toId 的定位逻辑以及官方测试所锁定的行为边界,能帮助你在自定义 addon 中写出更稳健的导航功能。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391