컴포넌트 주도 개발(Component-Driven-Development)
컴포넌트를 모듈 단위로 개발하여 사용자 인터페이스 구축에 도달하는 개발 및 설계 방법론
기본적인 컴포넌트 단위부터 시작해서 UI view를 구성하기 위해 점진적으로 조립해가는 상향적 특징을 가지고 있다.
장점
- 디자인 체계화, 디자이너와 효율적인 협업
- 재사용성: 다른 프로젝트에서도 쉽게 쓰고 공유 가능
- Decoupling: CSS, JS, I18n, UI 단위 테스트 등
UI 패턴이 3회 이상 사용되는 경우. 재사용 가능한 UI 컴포넌트로 간주한다.
UI 컴포넌트가 3개 이상의 프로젝트/팀에서 사용되는 경우, 디자인 시스템에 포함시킨다.
install
npx -p @storybook/cli sb init
npm run storybook
글로벌 스타일 추가하기
컴포넌트가 제대로 보이기 위해 글로벌 스타일을 적용. Styled-components의 글로벌 스타일 태그를 이용해서 추가.
// src/shared/global.js
import { createGlobalStyle, css } from 'styled-components';
import { color, typography } from './styles';
export const fontUrl = '<https://fonts.googleapis.com/css?family=Nunito+Sans:400,700,800,900>';
export const bodyStyles = css`
/* same as before */
`;
export const GlobalStyle = createGlobalStyle`
body {
${bodyStyles}
}
`;
// .storybook/preview.js
import React from "react";
import { GlobalStyle } from "../src/shared/global";
export const decorators = [
(Story) => (
<>
<GlobalStyle />
<Story />
</>
),
];
decorator는 어떤 스토리가 선택되었든 간에 GlobalStyle이 반드시 렌더링 되도록 한다.
폰트 태그 추가하기
가장 쉬운 방법은 .storybook/preview-head.html 파일에서 <head> 태그에 직접 <link> 태그를 추가하는 것이다.
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito+Sans:400,700,800,900" />
그외 방법
GitHub - storybookjs/design-system: 🗃 Storybook Design System
export default
export default metadata는 스토리북이 스토리를 나열하고 addons에서 사용하는 정보를 제공하는 방법을 제어한다.
// Button.stories.tsx
export default {
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
define story
CSF 파일의 명명된 export를 사용하여 컴포넌트의 스토리를 정의한다. story export에는 UpperCamelCase를 사용하는 것이 좋다.
ex) default 상태에서 primary로 button을 렌더링하는 코드를 export하는 방법
export const Primary: ComponentStory<typeof Button> = () => <Button primary>Button</Button>;
React Hooks로 작업
React Hooks를 사용하여 버튼의 상태를 변경하는 방법
export const Primary = () => {
const [value, setValue] = useState('Secondary');
const [isPrimary, setIsPrimary] = useState(false);
const handleOnChange = () => {
if(!isPrimary) {
setIsPrimary(true);
setValue('Primary');
}
};
return <Button primary={isPrimary} onClick={handleOnChange} label={value} />;
story 작성 방법
// Button.stories.js|jsx
import React from 'react';
import { Button } from './Button';
export default {
/* 👇 The title prop is optional.
* See <https://storybook.js.org/docs/react/configure/overview#configure-story-loading>
* to learn how to generate automatic titles
*/
title: 'Button',
component: Button,
};
export const Primary = () => ;
export const Secondary = () => ;
export const Tertiary = () => ;
args 사용
args를 도입하여 패턴을 개선. 유지 관리해야 하는 코드를 줄인다.
// Button.stories.ts|tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from './Button';
export default {
/* 👇 The title prop is optional.
* See <https://storybook.js.org/docs/react/configure/overview#configure-story-loading>
* to learn how to generate automatic titles
*/
title: 'Button',
component: Button,
} as ComponentMeta;
//👇 We create a “template” of how args map to rendering
const Template: ComponentStory = (args) => ;
//👇 Each story then reuses that template
export const Primary = Template.bind({});
Primary.args = { backgroundColor: '#ff0', label: 'Button' };
export const Secondary = Template.bind({});
Secondary.args = { ...Primary.args, label: '😄👍😍💯' };
export const Tertiary = Template.bind({});
Tertiary.args = { ...Primary.args, label: '📚📕📈🤓' };
💡 Template.bind({})은 표준 자바스크립트 기술 함수의 복사본을 만들기 위해. Template내보낸 각 스토리가 고유한 속성을 설정할 수 있도록 복사합니다 .
story에 args를 도입하면 작성해야하는 코드의 양을 줄일 수 있고 중복도 줄일 수 있다.
또한 다른 story를 작성할 때 재사용 할 수 있다.
데코레이터 사용
데코레이터는 스토리를 렌더링할 때 컴포넌트를 임의의 마크업으로 래핑하는 매커니즘.
// Button.stories.ts|tsx
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Button } from './Button';
export default {
/* 👇 The title prop is optional.
* See <https://storybook.js.org/docs/react/configure/overview#configure-story-loading>
* to learn how to generate automatic titles
*/
title: 'Button',
component: Button,
decorators: [
(Story) => (
),
],
} as ComponentMeta;
둘 이상의 컴포넌트가 필요한 story
디자인 시스템이나 컴포넌트를 구축할 때 두개 이상의 컴포넌트가 복합되는 경우가 있을 수 있다.
예를 들어 List 컴포넌트가 ListItem 컴포넌트를 포함하는 경우
// List.stories.ts|tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { List } from './List';
import { ListItem } from './ListItem';
//👇 Imports a specific story from ListItem stories
import { Unchecked } from './ListItem.stories';
export default {
/* 👇 The title prop is optional.
* See <https://storybook.js.org/docs/react/configure/overview#configure-story-loading>
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
} as ComponentMeta;
const ListTemplate: ComponentStory = (args) => {
const { items } = args;
return (
{items.map((item) => (
))}
)};
export const Empty = ListTemplate.bind({});
Empty.args = { items: [] };
export const OneItem = ListTemplate.bind({});
OneItem.args = {
items: [
{
...Unchecked.args,
},
],
};
배포
'FRONT-END > REACTJS' 카테고리의 다른 글
Render Props pattern (0) | 2023.02.22 |
---|---|
리덕스 라이브러리 (0) | 2022.02.19 |
Context API (0) | 2022.02.19 |
react 비동기 작업 (0) | 2022.02.19 |
react route로 SPA 개발 (0) | 2022.02.19 |