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

如何用React Router布局路由包裹Storybook故事以继承全局样式?

问题:在Storybook中用React Router布局路由为组件提供全局样式

我有一个简单的ComponentX组件,只渲染带样式的HTML,不需要数据获取或路由配置,对应一个Storybook故事。这个组件是为深色主题网站设计的,必须继承color: white;这类全局样式才能正常显示。

这些上下文样式(比如background-color: black;和color: white;)由GlobalStyles组件应用到<body>上,该组件用css-in-js库Emotion给文档加样式,代码如下:

import { Global } from '@emotion/react';
import { Outlet } from 'react-router-dom';

export const GlobalStyles = () => (
  <>
    <Global styles={{ body: { backgroundColor: 'black', color: 'white' } }} />
    <Outlet />
  </>
);

这个组件不接受子元素,而是作为React Router的布局路由使用,渲染<Outlet />。应用里的路由树结构如下(标记(1)的是布局路由):

<Router>
  <Routes>
    <Route element={<GlobalStyles/>} >      {/* (1) 布局路由 */}
      <Route path="login">
        <Route index element={<Login />} />
        <Route path="multifactor" element={<Mfa />} />
      </Route>
    </Route>
  </Routes>
</Router>

<Login>和<Mfa>页面都会调用ComponentX,在应用里这种方式能正常工作。

但在Storybook故事里直接渲染ComponentX时,因为缺少<body>上的全局样式,组件很难看清。显然应该创建一个装饰器,用这个布局路由<Route element={<GlobalStyles/>}>包裹每个故事,但该怎么实现?

我当前的component-x.stories.tsx代码如下(效果未达预期):

import React from 'react';

import ComponentX from './ComponentX';

export default {
  component: ComponentX,
  title: 'Component X',
};

const Template = args => <ComponentX {...args} />;

export const Default = Template.bind({});
Default.args = {};
Default.decorators = [
  (story) => <div style={{ padding: '3rem' }}>{story()}</div>
];

(我知道可以把<GlobalStyles>改成包裹整个<Router>的简单组件,但我想沿用这个模式,为依赖其他中间布局路由的组件编写故事。)


解决方案

要实现这个需求,你可以在Storybook装饰器中搭建一个极简的React Router环境,让布局路由的<Outlet>能正确渲染故事内容,具体做法:

  • 在故事文件中导入React Router的必要组件:MemoryRouter、Routes、Route
  • 编写装饰器,用MemoryRouter包裹整个结构,在Routes中配置布局路由,并将故事内容作为嵌套路由的element

修改后的component-x.stories.tsx代码如下:

import React from 'react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';

import ComponentX from './ComponentX';
import { GlobalStyles } from './path-to-global-styles'; // 替换为GlobalStyles的实际文件路径

export default {
  component: ComponentX,
  title: 'Component X',
};

const Template = args => <ComponentX {...args} />;

export const Default = Template.bind({});
Default.args = {};
Default.decorators = [
  (story) => (
    <MemoryRouter initialEntries={['/']}>
      <Routes>
        {/* 复用应用中的布局路由结构 */}
        <Route element={<GlobalStyles />}>
          {/* 让布局路由的Outlet渲染故事内容 */}
          <Route index element={story()} />
        </Route>
      </Routes>
    </MemoryRouter>
  ),
];

关键说明

  • 使用MemoryRouter:Storybook不需要依赖浏览器URL,MemoryRouter是React Router提供的内存路由,非常适合在测试或Storybook这类环境中使用
  • 匹配应用路由结构:和应用里的路由嵌套逻辑一致,GlobalStyles作为父路由的element,其内部的<Outlet>会自动渲染子路由的element(也就是我们的故事内容)
  • 保留原有样式:如果需要保留之前的padding装饰,可以把story()包裹在对应的div里:
Default.decorators = [
  (story) => (
    <MemoryRouter initialEntries={['/']}>
      <Routes>
        <Route element={<GlobalStyles />}>
          <Route index element={<div style={{ padding: '3rem' }}>{story()}</div>} />
        </Route>
      </Routes>
    </MemoryRouter>
  ),
];

这种方式完全复用了应用中的布局路由模式,后续遇到依赖其他中间布局路由的组件,都可以用同样的思路编写故事。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 06:45:31