React Vue Angular Web Components More
스토리북은 TypeScript 지원이 내장되어 있어 설정 없이 바로 시작할 수 있어요.
현재 프리뷰 상태인 CSF Next는 훨씬 향상된 TypeScript 지원을 제공해요. 대부분의 경우 별도의 명시적 타이핑 없이도 컴포넌트 타입을 자동으로 추론해주죠. 그래서 새로 만드는 TypeScript 스토리에는 CSF Next를 사용하시는 걸 추천드려요. 커스텀 인자(custom args)를 사용할 때만 직접 타입을 추가해주면 된답니다.
스토리를 작성할 때 타입을 지정하면 도움이 되는 두 가지 측면이 있어요.
첫 번째는 컴포넌트와 그 스토리들을 설명하고 구성하는 컴포넌트 메타(component meta)예요. CSF 파일에서는 default export 하는 부분이 바로 여기죠. 두 번째는 스토리 그 자체들이에요.
스토리북은 이를 위해 Meta와 StoryObj라는 유틸리티 타입을 제공합니다. 이 타입들을 사용한 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;
Meta와 StoryObj 타입은 둘 다 제네릭(generics)이에요. 그래서 컴포넌트 타입이나 컴포넌트의 프롭스 타입을 선택적으로 파라미터로 넘겨줄 수 있죠 (예: Meta<typeof Button>에서 typeof Button 부분).
이렇게 하면 TypeScript가 유효하지 않은 인자(arg)를 정의하는 걸 막아주고, 모든 데코레이터, 플레이 함수(play functions), 또는 로더(loaders)가 함수 인자들의 타입을 정확히 알 수 있게 해준답니다.
위의 예시는 컴포넌트 타입을 전달한 경우예요. 프롭스 타입을 직접 전달하는 방법은 아래 '커스텀 인자 타이핑하기' 섹션에서 확인해 보세요.
TypeScript 4.9 버전 이상을 사용 중이라면, 새로운 satisfies 연산자를 활용해서 더 엄격하게 타입을 체크할 수 있어요. 이제 단순히 잘못된 인자뿐만 아니라, 필수 인자가 빠졌을 때도 타입 에러를 받을 수 있게 됩니다.
스토리의 타입을 적용할 때 satisfies를 쓰면, 여러 스토리에서 플레이 함수를 공유할 때 타입 안전성을 유지하는 데 큰 도움이 돼요. 만약 이게 없다면 TypeScript는 플레이 함수가 정의되지 않았을 수도 있다(undefined)는 에러를 던질 수 있거든요. satisfies 연산자를 쓰면 TypeScript가 플레이 함수가 정의되었는지 아닌지를 똑똑하게 추론해줍니다.
마지막으로, satisfies를 사용하면 StoryObj 제네릭에 typeof meta를 전달할 수 있어요. 이렇게 하면 TypeScript에게 meta와 StoryObj 타입 사이의 연결 고리를 알려주는 셈이죠. 덕번에 meta 타입으로부터 args 타입을 추론할 수 있게 됩니다. 다시 말해, 인자가 스토리 수준에서 정의될 수도 있고 메타 수준에서 정의될 수도 있다는 걸 TypeScript가 이해하게 되어서, 필수 인자가 메타에 정의되어 있다면 스토리에서 정의하지 않아도 에러를 내지 않게 된답니다.
가끔 스토리에 컴포넌트의 프롭스에는 포함되지 않는 인자를 정의해야 할 때가 있어요. 이럴 때는 교차 타입(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;