본문 바로가기
Next.js/Library

storybook

by tokkiC 2023. 1. 8.

react 컴포넌트는 재사용하여 생산성을 향상시켜준다
근데 그 컴포넌트 정보를 팀원에게 공유하자니 생각보다 이게 설명하기 힘들고 귀찮다

Next.js에서는 그런 당신을 위해 준비했습니다! 내가 말고 어느 대단하신분들이 만든 라이브러리! storybook!
컴포넌트 별로 문서화하고 테스트 해볼 수 있는 라이브러리라고 보면 될 것 같다

api 도 스웨거로 문서화하고 테스트해보듯이, 컴포넌트도 문서화로 쉽게 정리하고 조작하도록 돕는 것이다!

아직 감이 안잡혀서 이게 뭔가 싶지만 정리해보자

사용 세팅방법은 아래와 같다

리액트 앱(혹은 next.js) 설치 후 아래의 코드 실행하자

npx sb init --builder webpack5

그럼 설치 후 아래와 같이 묻는다

Do you want to run the 'eslintPlugin' migration on your project? 에 y를 눌러 yes 를 해주자 그럼 다시 주르르 설치된다

설치 중에 요런 에러가 뜰 것이다

https://github.com/storybookjs/eslint-plugin-storybook

 

GitHub - storybookjs/eslint-plugin-storybook: 🎗Official ESLint plugin for Storybook

🎗Official ESLint plugin for Storybook. Contribute to storybookjs/eslint-plugin-storybook development by creating an account on GitHub.

github.com

스토리북 사용법이 정의된 스토리북 공식 깃헙이다
아래 캡쳐부분을 보자

eslint 플러그인에 위의 extends 를 추가해주면 된다고 한다. 응 그렇단다...

요 파일에 들어가서 위에서 추가하란대로 기존 extends 에 아래와 같이 추가해줬다

npm도 7버전으로 마이그레이션 할거냐고 묻길래 y를 눌러서 "넹 해주시죠~"

스토리북 설치가 끝났다

npm run stroybook 
혹은
yarn run storybook

으로 스토리북을 실행해보면 다음과 같은 창이 뜬다
왼쪽의 사이드 탭에 몇개의 데모 스토리(기본 제공 컴포넌트)를 제공한다

왼쪽의 탭에서 button 을 눌러보자

저 파란 버튼을 클릭하면 아래탭의 actions 에서 이벤트를 볼 수 있고,
controls 탭에서는 버튼 컴포넌트의 텍스트나 색, 크기 등의 css 를 수정해볼 수 있다

상단의 Docs를 눌러보면 아래와 같은 형식으로 보여주는데 뭐 코드를 볼 수 있다는 점 외엔 크게 다른점은 모르겠다
여기서도 컴포넌트를 확인 및 테스트가 가능하다

이제 스토리북 웹페이지를 닫고, 폴더구조를 확인해보자

.storybook 폴더와 stories 폴더가 생겼다
.storybook 폴더는 스토리 전역 설정용 폴더이며
story 폴더엔 방금 막 써본 Botton 이란 데모 스토리도 tsx, css 파일로 보인다

Button.tsx 를 눌러보자
스토리북 페이지에서 값을 넣어봤던 것들이 여기서 Props 로 갖는 것들이었다 두둥!
즉, 컴포넌트의 Props 를 스토리북에서 대입해보며 테스트가 가능하다는 말이다

근데 스토리 폴더를 이렇게 따로 두어 관리하는 것보다, 각 컴포넌트와 같은 경로에서 스타일 파일과 함께 두는 것이 찾고 관리하기 쉽기 때문에, stories 폴더는 지워버리고 .storybook 폴더에서 main.js 를 아래와 같이 수정해주자

main.js 파일의 stories 를 바꿔주자. 각 페이지, 컴포넌트와 같은 경로에서 스토리북에 필요한 mdx 파일과 js 파일을 찾도록 해주는 것이당

위처럼 설정을 바꾸면 최상위 경로에 components 폴더를 만들어서, pages 폴더와 함께 사용하면 된다

components 폴더에 Button 폴더를 만들고 버튼 컴포넌트를 Button.tsx 에 구현, Button.stories.tsx 에는 구현한 버튼을 스토리 북에서 테스트 해 볼 수 있도록 추가로 설정해주자

만들어본 예시를 보며 이해하자

//Button.tsx

import { css, SerializedStyles } from "@emotion/react";
import styled from "@emotion/styled";
import { MouseEvent } from "react";

// 컬러 표를 타입으로 정해 해당 타입에서만 고르도록 하자
export type Color = "primary" | "secondary" | "danger" | "warning";

// color props 가 undefined 일 경우가 있으므로 ? 를 붙여주자
// onClick의 이벤트를 props 로 받을 수 있는데, 이벤트로 리턴되는 것은 없다고 타입에서 정해주자
export type Props = {
  children: string;
  color?: Color;
  onClick: (event: MouseEvent<HTMLButtonElement>) => void;
};

// Button 컴포넌트에서 color property 의 값을 받아서 스타일을 바꿔주기 위한 함수
// emotion 을 사용, css`` css 리터럴로 리턴해준다
// 이때의 리턴된 css 리터럴의 타입은 SerializedStyles 로 해주자. 직렬화된 스타일 이란 뜻이다
// color props 가 undefined 일 경우, 인자를 받지 않을 수 있으니 ?를 붙여주자
export const getColors = (color?: Color): SerializedStyles => {
  switch (color) {
    case "primary":
      return css`
        background: #6d5dfc;
        color: #e4ebf5e6;
      `;
    case "secondary":
      return css`
        color: #5e5c64e6;
      `;
    case "danger":
      return css`
        background: #dc3545e6;
        color: #e4ebf5e6;
      `;
    case "warning":
      return css`
        background: #ffca2ce6;
        color: #5e5c64e6;
      `;
    default:
      return css``;
  }
};

// color, children, onClick 을 props 로 받아 사용할 것이므로 타입을 지정해주자
export const Button = styled.button<Props>`
  all: unset;
  display: flex;
  justify-content: center;
  align-items: center;
  justify-self: center;
  user-select: none;
  cursor: pointer;
  font-size: 1.6rem;
  width: 15rem;
  height: 4rem;
  border-radius: 1rem;
  transition: all 0.4s ease;
  ${({ color }) => getColors(color)}  // props 로 받은 color 값으로 css 값을 동적으로 변경한다
  box-shadow: 0.5vmin 0.5vmin 1vmin #c8d0e7, -0.5vmin -0.5vmin 1vmin #ffffff;
  &:hover {
    opacity: 0.9;
  }
  &:active {
    box-shadow: 0.5vmin 0.5vmin 1vmin #c8d0e7 inset, // inset 은 그림자를 안으로 들어가게 해준다
      -0.5vmin -0.5vmin 1vmin #ffffff inset;
  }
`;

// color 가 undefined 일 경우, 기본으로 사용할 color props 를 정해주자
Button.defaultProps = {
  color: "primary",
};
// Button.stories.tsx

import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Button } from "./Button";


// export default 로 보내는 객체는 스토리북에 탭에 사용할 내용이다
// title 에는 스토리북에서 사용할 컴포넌트의 이름을 정해주고, 하위 토글이라면 / 로 들여쓰기처럼 사용가능하다
// component 에는 실제로 연결할 컴포넌트를 넣어주자
// 이 객체는 Button의 타입을 인자로 받는 ComponentMeta 타입으로 간주한다

export default {
  title: "Controls/Button",
  component: Button,
} as ComponentMeta<typeof Button>;


// template 는 받은 인자들로 속성을 갖는 Button 엘리먼트를 리턴해준다
// template 의 타입은 Button 의 타입을 인자로 갖는 ComponentStory 이다

const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;


// Template 에 빈객체에 엮어서 빈 속성의 BasicButton 을 만들어주면
// 해당 BasicButton는 사이드 탭의 토글에서 클릭하여 테스트 가능한 컴포넌트로 보여진다
// Button.tsx 에서 만든 버튼을 보며 조작해 볼 수 있다
// args 속성 값으로 객체를 추가하여, 테스트 사항을 추가 할 수 있어보인다
// 아래에서 children 은 button 의 children 과 같이 해당 요소에 보이는 텍스트이므로 수정이 가능하다

export const BasicButton = Template.bind({});
BasicButton.args = {
  children: "Button",
};

 

이제 yarn storybook 으로 버튼 컴포넌트를 테스트해볼 수 있게 되었다 와아

아직 막 배운터라 효용은 잘 모르겠지만 말이다...

댓글