Skip to content

Storybook을 이용한 Front 작업 방식 (front DEV flow with Storybook)

sunkyu_yoo edited this page Dec 1, 2020 · 10 revisions

Storybook을 이용한 Front 작업 방식

1차 작성: 조병건 @marulloc
2차 수정: 유선규 @sunkest - eslint(react/jsx-props-no-spreading)

우리의 얄팍한 지식으로 고민해본 결과,

Storybook

  1. UI testing tool이면서,
  2. UI style 설정을 편리하게 만들어주는 tool이다.

따라서, 만약 Button 컴포넌트에 대한 Atomic Design을 진행한다고 했을 때, 우리의 작업방식은 다음과 같다.


작업방식

🗂 Img
├── Img.interface.ts
├── Img.style.ts
├── index.stories.tsx
└── index.tsx


  1. [🗂Img.interface.ts]에 Interface 정의

    • Style에 사용할 인터페이스 (CSS 속성에 대한 인터페이스) interface ImgStyles { }

    • Props에 사용할 인터페이스 (변수에 대한 인터페이스) Style 인터페이스를 상속해서 Prop 속성들 추가 interface ImgProps extends ImgStyles{ } 물론, 파생될 컴포넌트를 작성하기 전에, 어떤 style과 어떤 props가 필요할 것인지 논의해야 된다.

  2. Storybook에 로드하기 위한, template 컴포넌트 작성 Storybook의 control을 사용하여 스타일들을 변경하기 위해 template, arg를 이용하여 작성한다.

    • [🗂Img.style.ts]에 Storybook 사용을 위한 템플릿용 Styled Component 작성

      const StyledImg = styled.img<ImgStyles>`
        object-fit: cover;
        width: ${({ width }) => width}rem;
        height: ${({ height }) => height}rem;
        border-radius: ${({ borderRadius }) => borderRadius}%;
        src: ${({ src }) => src};
      `;
      
      export default StyledImg;
      
    • [🗂index.tsx]에 Storybook 사용을 위한 템플릿용 tsx 작성

      /* TSX, for using storybook */
      const Img: React.FC<ImgProps> = ({ width, height, src, borderRadius }: ImgProps) => {
        return <StyledImg width={width} height={height} src={src} borderRadius={borderRadius} />;
      };
      
      export default Img;
      
    • [🗂index.stories.tsx]에 어쩌구 저쩌구.Stories.tsx 작성

      template.bind + args를 사용하는 방식을 이용해야, addon이 변수들을 config 하기 쉽고, storybook에서 스타일 속성 값을 다양하게 바꿔가면서, 입맛에 맞는 스타일을 정할 수 있다.

      (근데 꼭 args를 쓰지 않아도 storybook의 control 탭에서 속성 값 변경이 가능한 것으로 보임...)

      import React from "react";
      import Img from ".";
      import { Story } from "@storybook/react/types-6-0";
      import { ImgProps } from "./Img.interface";
      
      export default {
        title: "Img",
        component: Img,
      };
      
      const Template: Story<ImgProps> = (args) => <Img {...args} />;
      
      export const TemplateImg = Template.bind({});
      
      • storybook의 document에서 권장하는 대로 작성 했음에도 eslint에서 eslint(react/jsx-no-props-spreading)라는 에러를 발생시킨다. 이에 대해서는 맨 아래에 후술.

  1. [🗂Storybook]에서 Storybook을 이용하여, 파생될 컴포넌트의 속성값을 정한다. Storybook에서 스타일 값을 바꿔가면서,

    (ex) Magzine page에 등장하는 Img는 width가 300px, height는 400px이면 되겠다
    (ex) Album 표지에 등장하는 Img는 width가 100px, heiht 100px, border-radius는 50px이면 되겠다. 
    

    이렇게 파생될 컴포넌트에 대한 스타일 값을 확정한다.


  1. [🗂Img.style.ts]에 확정된 스타일 값을 기반으로, style option 객체를 선언한다. 위에서 정의한, style inteface를 따르면 된다.

    const MagazineImgStyles: ImgStyles = {
       width: 300,
       height: 400,
       borderRadius: 0,
     };
     const AlbumImgStyles: ImgStyles = {
       width: 100,
       height: 100,
       borderRadius: 0,
     };
    

  1. [🗂index.tsx]에 실제 view를 만들 때, 사용 할 컴포넌트를 반환하는 tsx 코드를 작성한다.

     /* TSX, for using in code line */
     const MagazineImg: React.FC<ImgProps> = ({ src }: ImgProps) => {
       return <StyledImg {...MagazineImgStyles} src={src} />;
     };
    
     const AlbumImg: React.FC<ImgProps> = ({ src }: ImgProps) => {
       return <StyledImg {...AlbumImgStyles} src={src} />;
     };
    
     export { MagazineImg, AlbumImg };
    
    • 이렇게 코드를 작성하면 eslint에서 eslint(react/jsx-no-props-spreading) 에러를 여기서도 발생시킨다. 이에 대해서는 맨 아래에 후술.

  2. [🗂FREE~] 이제 외부에서, props만 넘겨주면서 컴포넌트를 사용 할 수 있다.

    <MagazineImg src={"examp.img"} />


물론 style마저 props를 넘기게 만들었다면, 따로 tsx 코드를 작성안해도 된다.

그러나 props를 넣는 코드가 너무 길면, 가독성이 떨어지기 때문에, 따로 tsx코드를 만들어서 export한다.





파일 별 코드

🗂 Img
├── Img.interface.ts
├── Img.style.ts
├── index.stories.tsx
└── index.tsx


Img.interface.ts

import "styled-components";

interface ImgStyles {
  width?: number;
  height?: number;
  borderRadius?: number;
}

interface ImgProps extends ImgStyles {
  src?: string;
}

export type { ImgStyles, ImgProps };


Img.style.ts

import styled from "styled-components";
import { ImgStyles } from "./Img.interface";

/* Components, for using storybook */
const StyledImg = styled.img<ImgStyles>`
  object-fit: cover;
  width: ${({ width }) => width}rem;
  height: ${({ height }) => height}rem;
  border-radius: ${({ borderRadius }) => borderRadius}%;
  src: ${({ src }) => src};
`;

export default StyledImg;

/* Component options, for using in code line */
const MagazineImgStyles: ImgStyles = {
  width: 20,
  height: 20,
  borderRadius: 0,
};
const AlbumImgStyles: ImgStyles = {
  width: 15,
  height: 15,
  borderRadius: 0,
};

export { MagazineImgStyles, AlbumImgStyles };


index.stories.tsx

import React from "react";
import Img from ".";
import { Story } from "@storybook/react/types-6-0";
import { ImgProps } from "./Img.interface";

export default {
  title: "Img",
  component: Img,
};

const Template: Story<ImgProps> = (args) => <Img {...args} />;

export const TemplateImg = Template.bind({});


index.tsx

import React from "react";
import StyledImg, { MagazineImgStyles, AlbumImgStyles } from "./Img.style";
import { ImgProps } from "./Img.interface";

/* TSX, for using storybook */
const Img: React.FC<ImgProps> = ({ width, height, src, borderRadius }: ImgProps) => {
  return <StyledImg width={width} height={height} src={src} borderRadius={borderRadius} />;
};

export default Img;

/* TSX, for using in code line */
const MagazineImg: React.FC<ImgProps> = ({ src }: ImgProps) => {
  return <StyledImg {...MagazineImgStyles} src={src} />;
};

const AlbumImg: React.FC<ImgProps> = ({ src }: ImgProps) => {
  return <StyledImg {...AlbumImgStyles} src={src} />;
};

export { MagazineImg, AlbumImg };





eslint(react/jsx-no-props-spreading)

eslint-plugin-react에 포함된 규칙으로, 아래와 같이 React Component의 Props에 spread operator를 사용하는 것을 안티 패턴 으로 보고 에러를 발생시킨다.

const Button = props => {
  const { kind, ...other } = props;
  const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  return <button className={className} {...other} />;
};

이것이 안티 패턴인 이유는 다음 글과 react document에 따르면 다음과 같다.

  • 코드에서 어떤 props가 component에 주어지는지 명시적이지 않아 props에 문제가 생겼을 때 troubleshooting이 어렵다.
  • 브라우저 개발자 도구의 React Developer Tools에서도 추적하지 못한다. -> 역시 trouble shooting을 어렵게 한다.
  • 불필요한 rerendering이 일어날 가능성이 있다. -> 이에 대해서는 아래 블로그 글에서 확인할 수 있다.
  • https://codeburst.io/react-anti-pattern-jsx-spread-attributes-59d1dd53677f
  • React Document의 설명은 다음과 같다.

    • JSX Spread Attributes는 (DOM에서 invalid HTML attributes일 수도 있는) 불필요한 props가 함께 component에 들어갈 수 있으니 꼭 필요할 때만 사용해라(use sparingly) 라고 되어있다. -> 결국 위의 세번째와 같은 말
  • https://reactjs.org/docs/jsx-in-depth.html#spread-attributes

위에서 언급된 spread operator로 전달된 코드들은 변경될 가능성이 없는 css styles 요소만 전달하고 있어 위에서 언급된 조건에 해당되지 않는다고 판단하여, 긴 props 전달이 반복적으로 일어나는 것을 방지하기 위해 eslint에 해당 rule을 'warn' 단계로 완화하여 사용하기로 했다.

Clone this wiki locally