다들 스토리북을 사용하면서 겪었을 문제라고 생각된다. 여러 라이브러리와 함께 이용할 시 골치아프다는것..
지금부터 외부 라이브러리들과 스토리북을 함께 이용할때 스토리북을 설정하는 법을 알아보자!
먼저 react router를 사용한다면 컴포넌트에 BrowserRouter를 감싸주어야 합니다.
export default {
title: 'Components/SomeComponent',
component: SomeComponent,
decorators: [
(Story) => (
<BrowserRouter>
<Story />
</BrowserRouter>
),
],
} as Meta;
하지만 이 설정만 할시에는 useParams
, useLocation
에서 값을 알아듣지 못한 문제가 있습니다. 이 부분은 어떻게 해결할까요?
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-react-router-v6',
],
간단하게 storybook-addon-react-router-v6
addon을 설치후 main.js에 다음과 같이 넣어주면 됩니다.
parameters: {
reactRouter: {
routePath: URL.VOTE_DEADLINE_GENERATOR,
routeState: { articleId: '0', items: ['투표1', '투표2', '투표3'] },
},
},
또한 스토리 단에서 parameters에 다음과 같은 설정을 해주면 됩니다. 이때는 BrowserRouter
로 감싸면 안됩니다.
recoil을 이용하려면 recoilRoot가 컴포넌트에 감싸져있어야 합니다. RecoilRoot 같은 경우 모든곳에 감싸져 있어야하기에 preview.js의 decorators에 넣어줍니다.
export const decorators = [
mswDecorator,
(Story) => (
<ThemeProvider theme={theme}>
<RecoilRoot>
<Story />
</RecoilRoot>
</ThemeProvider>
),
];
이렇게 이용할 시 recoil은 문제없이 이용가능합니다. 하지만 recoil의 값에 따라 보이는 상태가 다른 컴포넌트를 스토리북에서 확인하고 싶다면 어떻게 할까요?
실제 프로젝트에서 SnackBar 컴포넌트를 이용하는데 이 컴포넌트는 recoil을 통한 isOpen 상태에 따라서 보임을 결정합니다. 하지만 default value가 false이기 때문에 스토리북에서는 빈 화면밖에 확일할 수 밖에 없습니다.
const MockSnackBar = () => {
const setSnackBarState = useSetRecoilState(snackBarState);
setSnackBarState({
isOpen: true,
message: '테스트입니다.',
});
return <SnackBar />;
};
const Template: Story = () => <MockSnackBar />;
export const DefaultSnackBar = Template.bind({});
이런 경우는 Snackbar.stories.tsx
내부에 다음과 같이 MockSnackBar를 선언해준뒤 recoil상태만 바꾸어서 return하는 컴포넌트를 만들어서 처리합니다.
스토리북을 이용할때 가장 설정하기 어려운 부분입니다.
react-query를 이용할때 고려해야하는것이 3가지가 있기 때문입니다.
react-query 자체를 스토리북에서 이용하기 위한것은 어렵지 않지만 추가적으로 비동기 통신을 위한 msw
, env
설정이 필요하기에 어려움이 있습니다.
const queryClient = new QueryClient();
export const decorators = [
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
];
실제 앱에서 이용하듯이 QueryClientProvider
를 감싸주면 됩니다.
주로 API_URL
을 숨기기 위해서 .env
파일을 이용할 것입니다. 하지만 .env
를 스토리북에서 이용하려면 따로 설정을 해주어야 합니다. (CRA 환경이 아닐경우)
.storybook 내에 webpack.config.js를 만들어서 dotenv 설정을 해줍니다.
const path = require('path');
const Dotenv = require('dotenv-webpack');
const envPath =
process.env.NODE_ENV === 'development'
? path.join(__dirname, '../webpack/.env.development')
: path.join(__dirname, '../webpack/.env.production');
module.exports = ({ config }) => {
config.plugins.push(
new Dotenv({
path: envPath,
}),
);
return config;
};
이렇게 설정하고 process.env.API_URL
을 찍어보면 storybook에서는 undefined가 나오게 될것입니다.
찾아보니 storybook에서 .env
에 설정한 값을 가져오려면 반드시 STORYBOOK_
식으로 prefix를 붙혀야한다고 합니다.
const API_URL = process.env.STORYBOOk ? process.env.STORYBOOK_API_URL : process.env.API_URL;
실제 프로젝트에서는 API_URL
을 다음과 같이 정의하였습니다.
먼저 storybook을 위한 msw를 초기화해줍니다.
npx msw init .storybook/public
그러면 .storybook내의 public폴더에 mockServiceWorker.js
가 생성될 것입니다.
그런후 main.js
의 staticDir에 storybook의 public을 가르키도록 합니다.
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-react-router-v6',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
staticDirs: ['./public'],
};
다음은 preview.js로 넘어가보겠습니다.
import { initialize, mswDecorator } from 'msw-storybook-addon';
initialize(); // msw-storybook-addon
export const decorators = [
mswDecorator, // msw-storybook-addon
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
];
// storybook에서 msw를 실행시키기 위한 코드
if (typeof global.process === 'undefined') {
const { worker } = require('../src/mock/browser');
worker.start();
}
먼저 msw-storybook-addon
을 이용하여 msw를 스토리북에서 이용할 수 있도록 설정합니다.
밑의 if문은 스토리북에서 msw를 작동시키기 위한 코드입니다. 스토리북에서는 global.process
가 undefined가 나오기 때문에 다음과 같은 분기 처리를 해주었습니다.
특정 컴포넌트는 모바일 환경에서만 보여야 할 경우가 있을겁니다.
하지만 storybook의 default는 데스크탑이기때문에 처음 보는 사람이 보면 아무 컴포넌트도 보이지 않는 화면을 보게되어서 어색함을 느낄 수도 있기에 default를 모바일로 변경해줄 필요가 있습니다.
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
export default {
title: 'Layout/MenuSlider',
component: MenuSlider,
decorators: [
(Story) => (
<BrowserRouter>
<Story />
</BrowserRouter>
),
],
parameters: {
viewport: {
viewports: INITIAL_VIEWPORTS,
defaultViewport: 'iphone6',
},
},
} as Meta;