news 2026/9/10 14:34:58

Storybook Addon API 从零导入:正确区分 storybook/preview-api 与 storybook/manager-api

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Storybook Addon API 从零导入:正确区分 storybook/preview-api 与 storybook/manager-api

Storybook Addon API 从零导入:正确区分 storybook/preview-api 与 storybook/manager-api

【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook

本文是一份针对 Storybook Addon 开发者的入门指南,核心讲解 Addon 开发时最基础也最关键的一步——如何正确 import 官方 Addon API 所需的模块。通过一个最小导入示例,带你厘清storybook/preview-api(控制与配置 Addon 行为)和storybook/manager-api(读写 Storybook 管理器 UI 与全局 API)各自的能力边界、背后的源码实现与真实使用场景,读完即可在自己的 Addon 工程里写出正确的导入语句。

从一段核心导入代码说起

Storybook 允许开发者以编程方式与 Storybook 交互,从而构建和分发自定义 Addon 以及其它增强 Storybook 能力的工具。这段能力被官方称为Addon API,而使用它的第一步,就是在 Addon 源码顶部完成模块导入。

在 Storybook 官方文档 docs/addons/addons-api.mdx 的 "Core Addon API"(核心 Addon API)一节中,导入被定义成下面这样一段最小示例(原文见 docs/_snippets/storybook-addons-api-imports.md):

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

示例同时照顾了 JS 与 TypeScript 两种写法(manager.js|ts),并适用于任意前端框架(renderer="common")。整体含义很明确:Addon 的"管理器端"代码同时运行在 Storybook 的 manager 与 preview 两个运行环境里,因此需要分别从两个不同的子路径取得 API

两个入口包的分工:manager-api 与 preview-api

依据 docs/addons/addons-api.mdx 第 9–16 行的官方定义,Storybook 的 API 通过两个不同的包暴露,且目的各不相同:

导入路径用途
storybook/preview-api用于控制并配置 Addon 自身的行为(如注册 UI 组件、通信频道、装饰器)
storybook/manager-api用于与 Storybook 的 manager UI 交互,或访问 Storybook API
  • storybook/preview-api面向Addon 的注册逻辑与行为定义:比如调用addons.add()注册面板/工具栏/标签页,调用addons.register()作为 Addon 的入口点,使用addons.getChannel()获取与 preview 通信的频道实例。
  • storybook/manager-api面向Addon UI 组件内运行时状态读写:典型如useStorybookApi()hook——它让你在 React 组件内完整访问 Storybook API 的方法(选中故事、操作 query 参数、打开编辑器等)。

把上例翻译成实际场景:Addon 的register代码通常导入并调用addons(来自preview-api);而 Addon 渲染出来的面板/工具栏 React 组件,则通过useStorybookApi(来自manager-api)驱动 UI 与状态。

为什么两个 import 缺一不可

从官方 API 文档可以确认一组相互呼应的能力矩阵:

  • preview-apiaddons提供:addons.add()(注册 UI 组件类型)、addons.register()(Addon 入口点,可拿到 StorybookAPI 实例)、addons.getChannel()(获取与 manager/preview 双向通信的频道)、addons.setConfig()(覆盖默认 UI 配置,如主题、侧边栏尺寸)、makeDecorator()(以官方 Addon 风格创建装饰器)。
  • manager-api提供一组 React hooks:useStorybookApiuseStorybookStateuseChanneluseAddonStateuseParameteruseGlobalsuseArgs等,均为上述 API 在组件层的"薄封装",能显著减少样板代码。

因此一个功能完整的 Addon 通常两个包都会用到,这正是导入片段里两行 import 并列存在的原因。

源码级验证:两个子路径导出什么

storybook/preview-api的导出实现

在 monorepo 中,storybook包的导出映射定义于 code/core/package.json(第 231–246 行),它把两个公共子路径分别指向独立的入口源码:

"./manager-api": { "types": "./dist/manager-api/index.d.ts", "code": "./src/manager-api/index.ts", "default": "./dist/manager-api/index.js" }, "./preview-api": { "types": "./dist/preview-api/index.d.ts", "code": "./src/preview-api/index.ts", "default": "./dist/preview-api/index.js" }

也就是说,你写的import { addons } from 'storybook/preview-api'最终会命中 code/core/src/preview-api/index.ts。该文件集中 re-export 了 Addon 相关的核心能力(节选):

  • export { addons, mockChannel } from './addons.ts':既导出单例addons,也导出测试用的mockChannel
  • export { makeDecorator } from './addons.ts':官方风格的装饰器工厂;
  • 其它与 Addon 无强关联的运行时导出则来自preview-webstore等模块(如DocsContextStoryStore)。

addons单例的来源在 code/core/src/preview-api/modules/addons/main.ts:该文件注释明确写着 "Enforce addons store to be a singleton"(强制 addons store 为单例),并以export const addons = getAddonsStore()导出。这从源码层面印证了:无论 Addon 被加载多少次,addons.register()等都作用于同一个全局 store。

storybook/manager-api的 hooks 实现

storybook/manager-api对应 code/core/src/manager-api/index.ts,其中export * from './root.tsx'暴露了组件层 hooks 的实际实现。查看 code/core/src/manager-api/root.tsx 可以看到这些 hooks 都是围绕useStorybookApi()构建的:

  • useStorybookApi():返回完整 API 对象(第 331 行);
  • useChannel(eventMap, deps):订阅事件并返回 emitter(第 363 行);
  • useParameter<S>(parameterKey, defaultValue?):读取当前故事的参数,未定义时回落到默认值(第 380 行);
  • useAddonState<S>(addonId, defaultState?):为指定 Addon 提供持久化状态(第 492 行);
  • useArgs():读取 / 更新故事 args(第 496 行);
  • useGlobals():读写全局变量 globals(第 515 行)。

这些实现细节解释了为什么文档推荐从manager-api导入 hooks:官方文档(docs/addons/addons-api.mdx 第 210 行)明确说明 hooks 是storybook/manager-api模块的延伸。

实战:导入之后能做什么

导入语句只是起点。在 docs/addons/addons-api.mdx 中,这段导入 snippet 被放在 "Core Addon API" 章节的段首,紧接着展开的是一系列可直接组合进你 Addon 的用法。下面给出与上述两个 import 一一对应的最小实战模式。

addons注册 Addon 面板

// my-addon/src/manager.js|ts —— Addon 入口 import { addons } from 'storybook/preview-api'; import { useStorybookApi } from 'storybook/manager-api'; // 1) 通过 register 注册 Addon 并拿到 StorybookAPI addons.register('my-addon', (api) => { // 2) 注册 UI 组件类型(panel / toolbar / tab 之一) addons.add('my-addon/panel', { type: 'panel', title: 'My Addon', render: ({ active }) => { // 3) 在组件内部使用 manager-api 的 hook 读取当前故事 const sbApi = useStorybookApi(); const story = sbApi.getCurrentStoryData(); return active ? <pre>{JSON.stringify(story, null, 2)}</pre> : null; }, }); });

其中关键点与参数(均可对照 docs/addons/addons-api.mdx 第 18–32 行):

  • addons.add(type, { title, render })type为要注册的 UI 组件类型,title将显示在 Addon 面板中,render是渲染 Addon UI 的函数;render会被传入active,当面板处于聚焦状态时activetrue
  • addons.register(id, callback):作为所有 Addon 的入口点,回调会收到 StorybookAPI 实例,后续api.selectStory()api.setQueryParams()api.openInEditor()等方法都从它而来。
  • 若需与 preview 通信,可在注册代码里通过addons.getChannel()拿到兼容 NodeJSEventEmitter的频道实例,用emit发事件、用on收事件。

用 hooks 增强 Addon 组件

若你的 Addon 依赖 Storybook 全局状态(globals),官方文档推荐这样组合(见 docs/addons/addons-api.mdx 第 244–254 行与useGlobals/useArgs相关片段):

import { useGlobals } from 'storybook/manager-api'; function LocaleToolbar() { const [globals, updateGlobals] = useGlobals(); return ( <select value={globals.locale} onChange={(e) => updateGlobals({ locale: e.target.value })} > <option value="en">English</option> <option value="zh">中文</option> </select> ); }

由于useStorybookState/useGlobals等 hook 会订阅 Storybook 内部状态,官方建议配合React.memouseMemouseCallback使用,避免因高频 re-render 拖慢 UI(docs/addons/addons-api.mdx 第 214、246 行)——这与上面看到的useStorybookApi订阅式实现是直接相关的。

常见误区与判断准则

结合源码与官方文档,可以总结出几条实用的判断准则,帮助你在写 import 时快速决策:

  1. 要在 Addon 入口/注册处做"注册、挂接、通信"这类事→ 从storybook/preview-api导入addonsmakeDecoratorgetChannel等 API。
  2. 要在组件渲染中读取 API、参数、全局状态并驱动 UI→ 从storybook/manager-api导入useStorybookApiuseParameteruseGlobalsuseAddonState等 hooks。
  3. 不要在 Addon 源码中把两个包混成同一个 import:它们由 code/core/package.json 定义为两个独立的子路径导出,分别映射到 code/core/src/preview-api/index.ts 与 code/core/src/manager-api/index.ts 两份入口。
  4. 测试场景preview-api额外导出了mockChannel(源码见 code/core/src/preview-api/index.ts),为需要在单测中模拟频道的 Addon 提供了便利。

延伸阅读

本文对应的导入片段只是入口,完整的能力清单与逐方法说明可继续阅读仓库内以下文件:

  • docs/addons/addons-api.mdx:Addon API 完整参考,涵盖addons.add()register()getChannel()makeDecorator()、Storybook API 各方法与全部 hooks,以及与 Addon 类型(docs/addons/addon-types.mdx)、编写指南(docs/addons/writing-addons.mdx)的互链入口;
  • code/core/src/preview-api/modules/addons/main.ts:addons单例 store 的实现;
  • code/core/src/manager-api/root.tsx:manager-api 各 hooks(useStorybookApiuseChanneluseAddonStateuseGlobalsuseArgs等)的具体实现;
  • code/core/package.json:./preview-api./manager-api两个子路径的导出映射定义。

掌握storybook/preview-apistorybook/manager-api的导入分工,就掌握了 Storybook Addon 开发的第一块基石——无论你接下来要写面板、工具栏、标签页还是装饰器,所有 API 都从这一行 import 开始。

【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 14:30:00

YOLOv5+ArcFace人脸检测与特征提取工程闭环实践

简介&#xff1a;本资源是一套基于YOLOv5与ArcFace的人脸检测与识别完整实现方案&#xff0c;面向计算机视觉初学者及AI项目开发者&#xff0c;解决从人脸定位到特征匹配的一体化技术落地问题&#xff0c;适用于安防监控、门禁系统、身份核验等实际场景。压缩包共54个文件&…

作者头像 李华
网站建设 2026/9/10 14:25:50

Spring Boot拦截器中获取requestBody的最佳实践

1. 为什么需要获取requestBody&#xff1f; 在Spring Boot开发中&#xff0c;拦截器(Interceptor)是处理HTTP请求的重要组件。但很多开发者都遇到过这样的困境&#xff1a;在拦截器的preHandle方法中&#xff0c;无法直接获取到请求体(requestBody)的内容。这主要是因为Servlet…

作者头像 李华