초기세팅부터 실행까지 정말 5분도 걸리지 않는 ... 매우 간단합니다
// 초기세팅
npx sb init
// 추가적으로 내가 설치해준 것
yarn add --dev react-docgen-typescript-loader
yarn storybook
or
npm run storybook
크게
.storybook
폴더와stories
폴더가 생성되는데,.storybook
폴더 내부의 파일들은 전역적인 설정 관련된 것들을,stories
폴더 내부에는 예시가 되는 컴포넌트와 스토리들이 있습니다!
.storybook/main.js
→ stories를 위한 config 설정들 const path = require('path');
// storybook을 위한 config설정들
module.exports = {
// stories 파일이 어디에 있는지 경로 설정
stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
// addons 세팅 , 유니버스엔 하얀 컴포넌트들이 많아서 다크모드를 적용시켜줌
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'storybook-addon-styled-component-theme/dist/preset',
'storybook-dark-mode',
],
webpackFinal: async (config) => {
// node_mules폴더와 styles 폴더 안의 모듈을 인식할 수 있게 함
config.resolve.modules = [path.resolve(__dirname, '..'), 'node_modules', 'styles'];
// 절대 경로 설정
config.resolve.alias = {
...config.resolve.alias,
'@components': path.resolve(__dirname, '../components'),
'@assets': path.resolve(__dirname, '../public/assets'),
};
return config;
},
};
.storybook/preview.js
→ 모든 story들에 글로벌하게 적용될 포맷 세팅 // 모든 stort들에 글로벌하게 적용될 포맷을 세팅
// npx sb init을 통해 세팅된 값
// controls - 개발자가 코드를 수정하지 않아도 storybook에서 동적으로 인터렉션 가능하도록 .
export const parameters = {
// Global 하게 argType에 on 으로 시작하는 이벤트 핸들러 함수들을 모두 허용하는 정규식을 적어주면, Action 탭에서 이벤트가 발생하는 것을 감지할 수 있음
actions: { argTypesRegex: "^on[A-Z].*" },
// 해당 데이터타입을 가진 속성을 만났을 때 정규표현식을 통해 데이터타입에 따라 storybook은 이들을 적절하게 테스팅할 수 있도록 매칭해줄 것.
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
// 글로벌 스타일 적용시키고 싶다면 여기서 적용시켜줄 수 있음 (추후 decorator같은 것들)
스토리는 쉽게 말해 하나의 컴포넌트가 실행 가능한 하나의 케이스를 의미합니다. 특정한 props를 넘겼을 때, 그 자체가 하나의 스토리가 되고, 그렇게 다양한 스토리들을 정의하면 우리는 스토리북에서 모든 스토리들을 직관적으로 확인할 수 있습니다.
// 어떤 이름으로 스토리북에 올릴 것인지, 어떤 설정으로 렌더링할지 정의
export default {
title: 스토리북에 올릴 component폴더 계층 구조,
component: 스토리를 만들 컴포넌트 이름
}
export const 스토리이름 = () => 해당스토리에서 테스트할 인자가 담긴 컴포넌트
import React from 'react';
import styled from '@emotion/styled';
interface ButtonProps {
label: string;
size?: 'small' | 'large';
onClick?: () => void;
}
export default function Button({label, size, ...props}:ButtonProps) {
return (
<Styled.Button size={size}>
<div className="button" role="button" {...props}>
{label}
</div>
</Styled.Button>
)
}
const Styled = {
Button:styled.div<{size: 'small'|'large'}>`
display: flex;
justify-content: center;
align-items: center;
width: ${({ size }) => (size === 'small' ? '48px' : '76px')};
border-bottom: 1px solid #FFFFFF;
.button{
padding-top: 11px;
padding-bottom: 11px;
cursor: pointer;
color: #FFFFFF;
text-align: center;
font-family: JejuMyeongjo;
font-size: 14px;
}
`,
}
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Button from './Button';
// 어떤 컴포넌트의 story인지, 어떤 설정으로 렌더링할지 정의
export default {
title: 'stories/Button',
component: Button,
} as ComponentMeta<typeof Button>;
// 기본 포맷을 정해두고 bind로 제어
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
// 각각이 새로운 스토리들
// export const Small = () => <Button size="small" label="button" />; 얘와 같음
export const Small = Template.bind({});
Small.args = {
size: 'small',
label: 'Button',
};
export const Large = Template.bind({});
Large.args = {
size: 'large',
label: 'Button',
};
간단한 컴포넌트에 대해 스토리를 작성하는 것은 이게 전부이지만, 추가적으로 알아두면 좋을 것들에 대해 정리해보았습니다.
스토리북에 보여질 때에 어떻게 보여질 지 스타일을 적용시킬 수 있습니다. 다양한 범위에 같은 스타일을 중복 적용 시키면 중복으로 적용되므로 이 점도 유의하시면 좋을 것 같습니다.
//Button.stories.tsx
Small.decorators = [(Story) => <div style={{ margin: '3em' }}><Story/></div>];
//Button.stories.tsx
export default {
title: 'stories/Button',
component: Button,
decorators: [
(Story) => (
<div style={{ margin: '3em' }}>
<Story/>
</div>
),
],
} as ComponentMeta<typeof Button>;
//preview.js
import React from 'react';
// styled-component를 사용해도 무방합니다. 저는 next와 함께 사용해서 emotion을 사용하였습니다.
import { ThemeProvider } from '@emotion/react';
const theme = {
margin: '10px',
}
export const decorators = [
(Story) => (
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
),
];
Parameters를 통해 스토리북 내부에서 우리가 코드를 변형시키지 않고 테스트 해볼 수 있는 옵션들을 제공할 수 있습니다.
//Button.stories.tsx
Small.parameters = {
backgrounds: {
values: [
{ name: 'black', value: '#000000' },
{ name: 'green', value: '#0f0' },
],
},
};
//Button.stories.tsx
export default {
title: 'stories/Button',
component: Button,
decorators: [
(Story) => (
<div style={{ margin: '3em' }}>
<Story/>
</div>
),
],
parameters: {
backgrounds: {
values: [
{ name: 'black', value: '#000000' },
{ name: 'green', value: '#0f0' },
],
},
},
} as ComponentMeta<typeof Button>;
//preview.tsx
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
// 추가
backgrounds: {
values: [
{ name: 'black', value: '#000000' },
{ name: 'green', value: '#0f0' },
],
},
}
말 그대로, 스토리북에 보여질 이름을 바꾸는 설정입니다.
//Button.stories.tsx
Small.storyName = 'I am small button';
스토리북에서는 컴포넌트를 수정했을 때, 그 컴포넌트가 제대로 만들어진 컴포넌트인지 테스트 할 수 있는 기능을 제공해줍니다. 이를 위해선 스토리북을 배포해야하는데, 스토리북을 아래 사이트에서 배포하고 나면 기존 브랜치에서 새로운 브랜치를 따서 pr을 날렸을 때 해당 pr에서 변경된 사안이 제대로 테스트 되었는지 눈으로 확인할 수 있도록 해줍니다.
기존 브랜치에서 새로운 브랜치를 따서 pr을 날렸을 때 해당 pr에서 변경된 사안이 제대로 테스트 되었는지 직접 확인할 수 있습니다!
그리고 아래와 같이 변경 사항이 어떻게 적용되었는지에 대해서도 직접 확인하고 해당 pr을 머지할 수 있습니다.
안녕하세요 : ) 글 잘봤습니다!!
궁금한게 있는데 그럼 storybook에 작성한 Button등의 컴포넌트를 export해서
작성한 Button을 특정 컴포넌트에 import해서 사용하는 형태가 맞을까요?!