본문 바로가기
Dev/Vue.js

[Vue] Storybook 사용하는 법

by YummYum 2025. 1. 15.

 

Storybook은 UI 컴포넌트와 페이지를 개발, 테스트, 문서화하기 위한 도구입니다. Vue로 복잡한 UI를 간단한 컴포넌트로 분해하는 데는 도움이 되지만 프로젝트가 커짐에 따라 컴포넌트가 수가 늘어나고, 시간이 지남에 따라 어떻게 개발했는지 점점 잊히면서 문제가 발생합니다. 또한 어떤 컴포넌트에서 문제가 발생했을 때 해당 페이지로 이동해서 확인해야 합니다. (만약 특정 타입의 회원만 가능하다면 좀 더 골치가 아파집니다ㅎㅎ) 

 

그래서 저는 프로젝트를 시작하면 다른 건 몰라도 Storybook 세팅은 반드시 하는 편입니다. 컴포넌트 문서화도 자동으로 되며 props를 전달하거나, 데이터를 mock 하거나, 이벤트를 위조하여 특정 변형을 격리하여 렌더링 할 수 있습니다. 테스트 코드를 추가해 시각적으로 해당 컴포넌트가 어떻게 작동하는지 볼 수 있습니다. 협업하는 데는 장점이 많은 도구입니다. 만약 새로운 페이지를 개발해야 한다면 Storybook부터 들어가서 필요한 컴포넌트의 문서와 테스트 코드를 확인해 사용법만 익혀 개발을 하면 됩니다.

 


 

Storybook 설치하는 법

npx storybook init

 

Storybook 시작하는 법

npm run storybook

 


 

파일 *.stories.js|ts은 구성 요소의 모든 스토리를 정의합니다. 사이드바의 폴더 구성은 스토리에 title를 명시적으로 사용하여 사이드바에서 스토리의 위치를 ​​정의할 수 있습니다. /로 구분하고 동일한 title이 있을 경우 에러가 발생합니다.

 

Docs 페이지는 자동으로 생성된 문서입니다. 하단에 Control를 사용하면 구성 요소 args와 동적으로 상호 작용할 수 있습니다. Action은 콜백이 상호작용을, Interaction은 구성요소 테스트를, Visual Test는 UI 테스트를, Accessibility는 웹 접근성을 확인할 수 있습니다.

 

** .storybook/preview.js에 tags: ['autodocs']를 추가하면 문서가 자동으로 생성됩니다.

 

 

args : props, slots, 스타일, 입력 등 동적으로 변경하는 데 사용해 기본 컴포넌트 코드를 수정하지 않고 편집할 수 있습니다.

parameters : 스토리에 대한 정적인 메타 데이터입니다. 스토리, 구성 요소, 글로벌로 있고 구체적인 parameters가 우선됩니다.

decorators : 스토리를 추가로 래핑 하는 방법입니다. 일반적으로 마크업이나 컨텍스트를 감싸는 데 사용합니다. (글로벌, 구성요소, 스토리 순서대로 안쪽의 데코레이터에서 시작하여 바깥쪽으로 작업이 됩니다.)

play : 스토리가 렌더링 되고 실행되는 코입니다. 사용자 시나리오를 테스트할 수 있습니다.

 

import { expect, userEvent, within } from '@storybook/test'
import ProtectedInput from '@/components/atoms/ProtectedInput.vue'

export default {
  title: 'components/atoms/ProtectedInput',
  component: ProtectedInput,
  parameters: {
    componentSubtitle: '중요정보를 보호하는 Input 컴포넌트입니다.',
  },
}

export const Default = {
  decorators: [
    () => ({
      template: '<form><story /></form>',
    }),
  ],
  args: {
    maxLength: 6,
    'data-testid': 'id',
  },
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement)
    const input = canvas.getByTestId('id')
    await step('입력과 삭제가 됩니다.', async () => {
      await userEvent.click(input)
      await userEvent.keyboard('1234567')
      await userEvent.keyboard('{delete}')
      await expect(input.value.length).toBe(5)

      await userEvent.keyboard('{backspace}')
      await expect(input.value.length).toBe(4)
    })
  },
}

 

댓글