
Storybook の parameters
Storybook の parameters は、Story や Component に静的なメタデータやパラメタを追加するための機能です。これらのパラメータは addon の設定や Story の表示方法のカスタマイズなどに利用されます。
export const parameters = {
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#f8f8f8' },
{ name: 'dark', value: '#333333' },
],
},
};
export default {
component: Button,
parameters: {
backgrounds: {
default: 'dark',
},
},
};
export const Primary = {
args: { },
parameters: {
backgrounds: {
default: 'light',
},
},
};
parameters は多くの addon やツールで利用されており、例えば Storybook で MSW を利用するための msw-storybok-addon や、 VRT(Visual Regression Test) などに利用できるスクリーンショットを生成する storycap あるいは storycap-testrun などの設定は parameters で記述します。
parameters の型安全性の問題
Storybook の parameters は TypeScript で使う際に大きな問題があります。デフォルトでは parameters の型は { [key: string]: any }
となってしまっています。
例えば、以下のようなコードを書いたとしても、TypeScript はエラーを検出できません:
export const Primary = {
parameters: {
backgrouds: {
default: 'light',
},
},
};
export const decorator = (Story, context) => {
const nonExistentValue = context.parameters.nonExistent.value;
return Story />;
};
これでは TypeScript による補完やエラー検知といった恩恵を受けることができません。特に大規模なプロジェクトや複数人あるいは AI を交えた開発では、型安全性の欠如で問題の検知が遅れると非常に面倒です。
TypeScript の module augmentation と declaration merging
この問題を解決するために、TypeScript の module augmentation と declaration merging という機能を利用できます
Declaration Merging
Declaration merging は、同じ名前の型定義が複数ある場合に、TypeScript がそれらをマージする機能です。特に interface は同じ名前で複数回定義することができ、それらの定義がマージされます。
interface Box { height: number; width: number; } interface Box { scale: number; } let box: Box = { height: 5, width: 6, scale: 10 };
Module Augmentation
Module augmentation は、既存のモジュールの型定義を拡張する機能です。declare module "モジュール名"
の形式で利用できます。
export class ObservableT> { } import { Observable } from "./observable"; declare module "./observable" { interface ObservableT> { mapU>(f: (x: T) => U): ObservableU>; } }
Storybook の parameters の型を拡張する
Storybook の parameters の型は Parameters
という interface で定義されています。この interface を前述の module augmentation と declaration merging を使って拡張することで、型安全に parameters を使うことができます。
基本的な型拡張の方法は以下の通りです:
import { Parameters } from '@storybook/react';
declare module '@storybook/react' {
interface Parameters {
myCustomParameter?: {
value: string;
enabled: boolean;
};
}
}
これにより、parameters.myCustomParameter
に型が付き、補完も効くようになります:
export const Primary = {
parameters: {
myCustomParameter: {
value: 'hello',
enabled: true,
},
},
};
実際の使用例
外部パッケージの parameters の型定義追加
多くの Storybook addon は parameters を使って設定を受け取りますが、これらのパッケージは独自の型定義を提供していることが多いです。これらの型定義を Storybook の Parameters に統合することで、型安全に使うことができます。
MSW の型定義追加
MSW を Storybook で使う場合、msw-storybook-addon
を利用します。このアドオンは MswParameters
という型を export しており、これを使って Parameters を拡張できます。
import type { Parameters } from '@storybook/react';
import type { MswParameters } from 'msw-storybook-addon';
declare module '@storybook/react' {
interface Parameters extends MswParameters {}
}
これにより、MSW のハンドラーを型安全に設定できます:
import { rest } from 'msw';
export const WithMock = {
parameters: {
msw: {
handlers: [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, name: '田中太郎' },
{ id: 2, name: '鈴木花子' },
])
);
}),
],
},
},
};
Storycap の型定義追加
Storycap はスクリーンショットを自動生成するアドオンで、ScreenshotParameters
という型を提供しています。これを使って Parameters を拡張できます:
import type { Parameters } from '@storybook/react';
import type { ScreenshotParameters } from "storycap-testrun";
declare module '@storybook/react' {
interface Parameters {
screenshot?: ScreenshotParameters;
}
}
これにより、Storycap の設定を型安全に行えます:
export const WithScreenshot = {
parameters: {
screenshot: {
delay: 1000,
},
},
};
自前の decorator から parameters を参照する
Storybook では decorator を使って共通の設定を適用することがよくあります。例えば、React Context の Provider など、広い範囲から参照される状態を設定する場合などです。
このように、各 Story ごとに異なる初期値を設定したい場合にも parameters を使うと便利です。
例えば、jotai や React Context などのグローバル状態管理を使っている場合、Story ごとに異なる初期状態を設定したいことがあります:
まず、parameters の型を拡張します:
import type { Parameters } from '@storybook/react';
import { CurrentUser } from '../src/types';
declare module '@storybook/react' {
interface Parameters {
initialState?: {
currentUser?: CurrentUser;
};
}
}
次に、この parameters を使用する decorator を作成します:
import { Decorator } from '@storybook/react';
import { UserProvider } from '../src/contexts/UserContext';
export const decorators: Decorator[] = [
(Story, ctx) => {
const currentUser = ctx.parameters.initialState?.currentUser ?? null;
return (
UserProvider initialUser={currentUser}>
Story />
/UserProvider>
);
},
];
これにより、各 Story で異なる初期状態を型安全に設定できます:
export const LoggedInUser = {
parameters: {
initialState: {
currentUser: {
id: 1,
name: 'Test User',
email: 'testusr@example.com',
},
},
},
};
このようにコンポーネントの外側で注入される設定や状態を Decorator + Parameters で渡すことで、型安全に・ Story の args と全体の設定・状態を分離しつつ記述できます。
まとめ
Storybook の parameters はデフォルトでは型安全性に欠けますが、TypeScript の module augmentation と declaration merging を活用することで、型安全に使うことができます。
また、parameters は外部パッケージが提供する addon だけでなく、プロジェクト独自の Context Provider への値の注入など、自作の Decorator でも利用することができます。
現代の TypeScript では型があたってないことは基本的に許されないぞ!という気持ちで日々やっていきましょう。
筆者 @izumin5210 が所属する バクラク事業部 PlatformEngineering部 では様々な技術領域について発信をしています。 興味がある方はカジュアル面談などもぜひ!
Views: 2