본문 바로가기
웹개발/리액트

📚 Storybook을 사용해보자!

by 현댕5697 2021. 7. 27.
반응형

📢 개발환경: React.js, typescript, styled-components 사용

 혹시 storybook 세팅이 되어있지 않다면 이전 게시물을 참고하자!

2021.07.25 - [웹개발/개발환경 설정] - next.js 에 typescript, storybook 설정하기

 

next.js 에 typescript, storybook 설정하기

🎠 내 tmi... 쿠키파킹의 웹사이트는 원래 react.js로 개발했었다. 쿠키파팅 서비스에는 디렉토리 공유 기능이 있다. 우리는 사용자들이 카톡으로 공유 링크를 전달하는 경우가 많을 것이라고 예측

carpediem9911.tistory.com

 

storybook을 사용하기 위해 블로그 글을 읽어보니 addon-knobs를 사용해서 컴포넌트 props를 제어하고 있었다.

하지만 storybook 공식 사이트를 들어가서 knobs에 대해 찾아보아도 관련 문서가 나오지 않았다.

storybook이 v6으로 업데이트 되면서 knobs 대신 controls를 사용하는 것으로 대체된 것 같았다.

https://stackoverflow.com/questions/65492043/difference-between-knobs-and-controls-in-storybook

 

difference between knobs and controls in Storybook?

I am new to the storybook. When I go through the documentation and videos about the storybook I read about knobs Addon. Knobs addon and control looks similar. What is the difference between those two

stackoverflow.com

controls와 actions를 사용하면 컴포넌트의 설정을 변경하며 테스트 할 수 있어서 storybook을 알차게 활용할 수 있다.

 

 

1. controls, actions 설정하기


addon의 controls와 actions를 사용하기 위해서는 먼저 라이브러리를 설치해야 한다.

package.json을 확인해서 아래 라이브러리가 설치되어있지 않다면 설치해주자.

npm i -D @storybook/addon-actions @storybook/addon-controls
# or
yarn add --dev @storybook/addon-actions @storybook/addon-controls

그 후 .storybook 폴더에 들어가서 main.js를 수정해주어야 한다.

// .storybook/main.js
module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/preset-create-react-app",
    "storybook-addon-styled-component-theme/dist/preset",
    "@storybook/addon-actions",
    "@storybook/addon-controls",
  ],
};

addons에 받은 라이브러리를 추가해주자.

그럼 이제 controls와 actions를 사용할 준비가 끝났다!

 

 

2. 컴포넌트 생성


일단 테스트해볼 컴포넌트를 만들어야 한다.

제일 만만한 버튼을 컴포넌트를 구현해보았다.

// Btn.tsx
import React from "react";
import styled, { css } from "styled-components";

export interface BtnProps {
  /** 버튼 안의 내용 */
  children: React.ReactNode;
  /** css (width, height, borderRadius, fontSize...) */
  style: React.CSSProperties;
  /**  버튼 eventhandler */
  onClick: React.MouseEventHandler<HTMLButtonElement>;
  /** 버튼 활성화 여부 (활성화 - >true / 비활성화 -> false) */
  isAtv?: boolean;
}
const Btn = ({ children, style, onClick, isAtv }: BtnProps) => {
  return (
    <BtnWrap style={style} onClick={isAtv ? onClick : undefined} isAtv={isAtv}>
      {children}
    </BtnWrap>
  );
};

export default Btn;

interface BtnWrapProps {
  style: React.CSSProperties;
  isAtv?: boolean;
}
const BtnWrap = styled.button<BtnWrapProps>`
  all: unset;
  box-sizing: border-box;
  cursor: pointer;

  width: 100%;
  height: 100%;

  font-weight: 500;
  text-align: center;
  line-height: ${(props) => props.style.height};
  color: var(--white);

  transition: 0.2s;
  ${(props) =>
    props.isAtv
      ? css`
          background-color: var(--blue_dark);
        `
      : css`
          background-color: var(--gray_5);
        `};
`;

props type을 작정해줄 때 주석을 달아주도록 하자. 

그러면 나중에 storybook을 실행해주었을 때 이렇게 친절한 description 부분을 확인할 수 있다.

만약 이렇게 docs가 생성되지 않는다면 react-docgen-typescript-loader 라이브러리를 설치하도록 하자.

 

 

3. story 생성


// Btn.stories.tsx
import React from "react";
import { Story, Meta } from "@storybook/react";
import Btn, { BtnProps } from "./Btn";

export default {
  title: "components/atoms/Btn",
  component: Btn,
  argTypes: {
    children: { type: "text" },
    isAtv: {
      control: { type: "boolean" },
    },
    style: {
      control: { type: "object" },
    },
    onClick: { action: "clicked" },
  },
} as Meta;

const Template: Story<BtnProps> = (args) => <Btn {...args} />;

export const button = Template.bind({});
button.args = {
  children: "button",
  isAtv: false,
  style: {
    width: "100px",
    height: "40px",
    borderRadius: "20px",
    fontSize: "13px",
    fontWeight: 700,
  },
  onClick: (e) => console.log(e),
};

 

1) args 설정

argTypes: {
  children: { control: { type: "text" } },
  isAtv: {
    control: { type: "boolean" },
  },
  style: {
    control: { type: "object" },
  },
},

이 부분은 Btn 컴포넌트의 props의 타입을 지정해주는 부분이다.

지정할 수 있는 타입 종류가 여러가지라서, 이 중에서 props에 알맞은 타입을 지정해주면 된다.

공식문서에는 위 처럼 사용하라고 나와있는데

argTypes: {
  children: { type: "text" },
  isAtv: {
    type: "boolean"
  },
  style: {
    type: "object"
  },
},

이렇게 작성해도 controls는 동작하는 것 같다.

 

만약 컴포넌트에 대한 args를 설정하고 싶다면 args를 export default 되는 곳에서 args를 설정하면 된다.

args: {
  children: "button",
  isAtv: true,
},

이 경우 모든 Btn stories는 isAtv=true, children="button"인 props를 넘겨 받을 것이다.

 

 

만약 여러 개의 Btn story를 만들 경우, 특정 story에 대해서만 args를 설정하고 싶다면 어떻게 해야할까?

이때는 button.arg를 작성해준 부분처럼 코드를 작성하면 된다.

그러면 button이라는 특정 story에 대해서만 args 값을 지정할 수 있다. 

export const button = Template.bind({});
button.args = {
  children: "button",
  isAtv: false,
  style: {
    width: "100px",
    height: "40px",
    borderRadius: "20px",
    fontSize: "13px",
    fontWeight: 700,
  },
  onClick: (e) => {
    console.log(e);
    return 1;
  },
};

export const button2 = Template.bind({});
button2.args = {
  children: "button",
  isAtv: false,
  style: {
    width: "140px",
    height: "50px",
    borderRadius: "5px",
    fontSize: "15px",
    fontWeight: 500,
  },
  onClick: (e) => {
    console.log(e);
    return 1;
  },
};

이 코드의 경우 2개의 story를 만들고 각각의 args 값을 지정해주었다.

storybook을 실행한 뒤 좌측 리스트를 보면

요렇게 button, button2 story가 생긴 것을 확인할 수 있다!

 

캔버스에의 하단 패널에선 button.arg에서 지정한 값이 설정되어있는 controls를 확인할 수 있다.

만약 캔버스에서 하단 패널이 안보인다면 'a' 키를 누르거나 스토리북 로고 옆의 메뉴 버튼을 클릭하여 'Show addons'가 켜져 있는지 확인하자. 혹은 'd' 키를 누르면 패널이 나타날 것이다! 

 

2) parameter 설정

args가 컴포넌트와 story가 렌더링 될 때 필요한 데이터를 지정해준다면, parameter는 storybook의 기능과 addon의 동작을 제어할 때 사용한다.

예를 들어 

button.parameters = {
  backgrounds: {
    values: [
      { name: "red", value: "#f00" },
      { name: "green", value: "#0f0" },
    ],
  },
};

button story에 대해 이렇게 parameter를 설정해주면

이렇게 배경 색깔을 옵션을 설정할 수 있다.

 

button.parameters = {
  actions: {
  	handles: ["click button"],
  },
};

이렇게 parameter를 설정해주면 button story 창에서 캔버스 안에서 button 태그에 대한 클릭 액션을 감지하여, 클릭할 때마다 Actions에 로그가 찍힌다.

Btn 컴포넌트에 className 속성을 'btnStory'라고 준 뒤 다음과 같이 parameter를 주었다면,

button.parameters = {
  actions: {
  	handles: ["click .btnStory"],
  },
};

button story 창에서 캔버스 안에서 'btnStory'라는 클래스 속성을 가진 태그에 대한 클릭 액션을 감지하여, 클릭할 때마다 Actions에 로그가 찍히게 된다.

 

3) actions 설정

action은 argTypes 에서 설정해주면 된다.

argTypes: {
  onClick: { action: "clicked" },
},

argTypes를 위의 코드처럼 설정해주고 button을 클릭해봐도 actions 패널에 로그가 찍히지 않았다.

어떤 부분이 문제였는지 살펴보니 

button.args = {
  children: "button",
  isAtv: false,
  style: {
    width: "100px",
    height: "40px",
    borderRadius: "20px",
    fontSize: "13px",
    fontWeight: 700,
  },
  onClick: (e) => console.log(e),
};

 

이 코드로 실행한 결과 actions 패널에서는 로그가 찍히지 않고, 개발자도구 콘솔창에서만 로그가 찍히는 것을 볼 수 있었다.

button의 args 값을 설정할 때, onClick에 대한 함수를 지정해주어서 그런 듯 하였다.

 

button.args = {
  children: "button",
  isAtv: false,
  style: {
    width: "100px",
    height: "40px",
    borderRadius: "20px",
    fontSize: "13px",
    fontWeight: 700,
  },
};

그래서 button.args 부분에서 onClick에 대한 부분을 빼준뒤에 버튼을 클릭해보았더니

actions 패널에 콜백함수에 대한 로그가 잘 찍히는 것을 확인할 수 있다.

 

actions 패널에 찍히는 값은 action을 걸어준 props에 들어오는 인자값인 듯 하다.

📌 example 

- Btn 컴포넌트에 이렇게 event handler가 설정되어 있는 경우,

onClick={(e) => onClick(e)}

버튼을 클릭할 때 마다 actions 패널에는 

이런식으로 e 값이 찍히게 된다.

- Btn 컴포넌트에  이렇게 event handler가 설정되어 있는 경우,

onMouseOver={() => onClick(1)}

버튼을 호버할 때 마다 actions 패널에는 

이렇게 로그가 찍히게 된다!

 

참고로 storybook을 설치할 때 .storybook/preview.js 에 자동으로 argsType을 매칭할 수 있도록 설정되어 있다.

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}

그래서 특별히 argTypes에서 action에 대한 부분을 명시하지 않아도 actions가 동작한다.

 

 

참고 사이트


https://storybook.js.org/

 

Storybook: UI component explorer for frontend developers

Storybook is an open source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation.

storybook.js.org

 

 

반응형

댓글