Stories/Writing stories in TypeScript

김동현·2026년 3월 22일

TypeScript로 스토리 작성하기 (Writing stories in TypeScript)

React Vue Angular Web Components More

스토리북은 TypeScript 지원이 내장되어 있어 설정 없이 바로 시작할 수 있어요.

현재 프리뷰 상태인 CSF Next는 훨씬 향상된 TypeScript 지원을 제공해요. 대부분의 경우 별도의 명시적 타이핑 없이도 컴포넌트 타입을 자동으로 추론해주죠. 그래서 새로 만드는 TypeScript 스토리에는 CSF Next를 사용하시는 걸 추천드려요. 커스텀 인자(custom args)를 사용할 때만 직접 타입을 추가해주면 된답니다.


Meta와 StoryObj로 스토리 타입 정의하기 (Typing stories with Meta and StoryObj)

스토리를 작성할 때 타입을 지정하면 도움이 되는 두 가지 측면이 있어요.
첫 번째는 컴포넌트와 그 스토리들을 설명하고 구성하는 컴포넌트 메타(component meta)예요. CSF 파일에서는 default export 하는 부분이 바로 여기죠. 두 번째는 스토리 그 자체들이에요.

스토리북은 이를 위해 MetaStoryObj라는 유틸리티 타입을 제공합니다. 이 타입들을 사용한 CSF 파일 예시를 함께 보시죠.

Button.stories.ts

// 여러분이 사용 중인 프레임워크(예: react-vite, nextjs, vue3-vite 등)로 교체하세요.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta = {
  component: Button,
} satisfies Meta<typeof Button>;
export default meta;
 
type Story = StoryObj<typeof meta>;
 
export const Basic = {} satisfies Story;
 
export const Primary = {
  args: {
    primary: true,
  },
} satisfies Story;

프롭스 타입 파라미터 (Props type parameter)

MetaStoryObj 타입은 둘 다 제네릭(generics)이에요. 그래서 컴포넌트 타입이나 컴포넌트의 프롭스 타입을 선택적으로 파라미터로 넘겨줄 수 있죠 (예: Meta<typeof Button>에서 typeof Button 부분).

이렇게 하면 TypeScript가 유효하지 않은 인자(arg)를 정의하는 걸 막아주고, 모든 데코레이터, 플레이 함수(play functions), 또는 로더(loaders)가 함수 인자들의 타입을 정확히 알 수 있게 해준답니다.

위의 예시는 컴포넌트 타입을 전달한 경우예요. 프롭스 타입을 직접 전달하는 방법은 아래 '커스텀 인자 타이핑하기' 섹션에서 확인해 보세요.


더 나은 타입 안전성을 위해 satisfies 사용하기 (Using satisfies for better type safety)

TypeScript 4.9 버전 이상을 사용 중이라면, 새로운 satisfies 연산자를 활용해서 더 엄격하게 타입을 체크할 수 있어요. 이제 단순히 잘못된 인자뿐만 아니라, 필수 인자가 빠졌을 때도 타입 에러를 받을 수 있게 됩니다.

스토리의 타입을 적용할 때 satisfies를 쓰면, 여러 스토리에서 플레이 함수를 공유할 때 타입 안전성을 유지하는 데 큰 도움이 돼요. 만약 이게 없다면 TypeScript는 플레이 함수가 정의되지 않았을 수도 있다(undefined)는 에러를 던질 수 있거든요. satisfies 연산자를 쓰면 TypeScript가 플레이 함수가 정의되었는지 아닌지를 똑똑하게 추론해줍니다.

마지막으로, satisfies를 사용하면 StoryObj 제네릭에 typeof meta를 전달할 수 있어요. 이렇게 하면 TypeScript에게 metaStoryObj 타입 사이의 연결 고리를 알려주는 셈이죠. 덕번에 meta 타입으로부터 args 타입을 추론할 수 있게 됩니다. 다시 말해, 인자가 스토리 수준에서 정의될 수도 있고 메타 수준에서 정의될 수도 있다는 걸 TypeScript가 이해하게 되어서, 필수 인자가 메타에 정의되어 있다면 스토리에서 정의하지 않아도 에러를 내지 않게 된답니다.


커스텀 인자 타이핑하기 (Typing custom args)

가끔 스토리에 컴포넌트의 프롭스에는 포함되지 않는 인자를 정의해야 할 때가 있어요. 이럴 때는 교차 타입(intersection type, &)을 사용해서 컴포넌트의 프롭스 타입과 커스텀 인자의 타입을 합칠 수 있습니다.

예를 들어, 자식 컴포넌트를 채우기 위해 footer라는 인자를 사용하는 방법은 이렇습니다:

CSF 3 | CSF Next 🧪

Page.stories.ts|tsx

// 여러분이 사용 중인 프레임워크로 교체하세요.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Page } from './Page';
 
// 👇 컴포넌트 프롭스와 커스텀 인자를 합칩니다.
type PagePropsAndCustomArgs = React.ComponentProps<typeof Page> & { footer?: string };
 
const meta = {
  component: Page,
  render: ({ footer, ...args }) => (
    <Page {...args}>
      <footer>{footer}</footer>
    </Page>
  ),
} satisfies Meta<PagePropsAndCustomArgs>;
export default meta;
 
type Story = StoryObj<typeof meta>;
 
export const CustomFooter = {
  args: {
    footer: 'Built with Storybook',
  },
} satisfies Story;
profile
프론트에_가까운_풀스택_개발자

0개의 댓글