-
Notifications
You must be signed in to change notification settings - Fork 3
Storybook을 이용한 Front 작업 방식 (front DEV flow with Storybook)
1차 작성: 조병건 @marulloc
2차 수정: 유선규 @sunkest - eslint(react/jsx-props-no-spreading)
우리의 얄팍한 지식으로 고민해본 결과,
Storybook은
- UI testing tool이면서,
- UI style 설정을 편리하게 만들어주는 tool이다.
따라서, 만약 Button 컴포넌트에 대한 Atomic Design을 진행한다고 했을 때, 우리의 작업방식은 다음과 같다.
🗂 Img
├── Img.interface.ts
├── Img.style.ts
├── index.stories.tsx
└── index.tsx
-
[🗂Img.interface.ts]에 Interface 정의
-
Style에 사용할 인터페이스 (CSS 속성에 대한 인터페이스)
interface ImgStyles { }
-
Props에 사용할 인터페이스 (변수에 대한 인터페이스) Style 인터페이스를 상속해서 Prop 속성들 추가
interface ImgProps extends ImgStyles{ }
물론, 파생될 컴포넌트를 작성하기 전에, 어떤 style과 어떤 props가 필요할 것인지 논의해야 된다.
-
-
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)라는 에러를 발생시킨다. 이에 대해서는 맨 아래에 후술.
-
-
[🗂Storybook]에서 Storybook을 이용하여, 파생될 컴포넌트의 속성값을 정한다. Storybook에서 스타일 값을 바꿔가면서,
(ex) Magzine page에 등장하는 Img는 width가 300px, height는 400px이면 되겠다 (ex) Album 표지에 등장하는 Img는 width가 100px, heiht 100px, border-radius는 50px이면 되겠다.
이렇게 파생될 컴포넌트에 대한 스타일 값을 확정한다.
-
[🗂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, };
-
[🗂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) 에러를 여기서도 발생시킨다. 이에 대해서는 맨 아래에 후술.
-
[🗂FREE~] 이제 외부에서, props만 넘겨주면서 컴포넌트를 사용 할 수 있다.
<MagazineImg src={"examp.img"} />
물론 style마저 props를 넘기게 만들었다면, 따로 tsx 코드를 작성안해도 된다.
그러나 props를 넣는 코드가 너무 길면, 가독성이 떨어지기 때문에, 따로 tsx코드를 만들어서 export한다.
🗂 Img
├── Img.interface.ts
├── Img.style.ts
├── index.stories.tsx
└── index.tsx
import "styled-components";
interface ImgStyles {
width?: number;
height?: number;
borderRadius?: number;
}
interface ImgProps extends ImgStyles {
src?: string;
}
export type { ImgStyles, ImgProps };
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 };
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({});
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-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' 단계로 완화하여 사용하기로 했다.