본문 바로가기

JavaScript/React.js

typescript 환경일 때 storybook 적용하기

기본 사용법

1. cli 설치

yarn add global @storybook/cli

2. storybook을 프로젝트 내에 설치

yarn getstorybook

3. storybook 실행

yarn storybook

 

typescript 사용법

여기까지가 바닐라 js 일 때 storybook 셋팅이고 typescript는 추가적인 setting이 필요하다.

 

1. .stories/0-Welcome.stories.js.stories/0-Welcome.stories.tsx로 변경한다.

 

storybook을 다시 키면 에러가 바바밤!! 발생한다!

 

2. @storybook/addon-info react-docgen-typescript-loaderbabel-preset-react-app 설치

yarn add -D @storybook/addon-info react-docgen-typescript-loader babel-preset-react-app

 

3. .storybook/main.js를 아래와 같이 수정한다.

module.exports = {
stories: ['../src/**/*.stories.tsx'],
webpackFinal: async config => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
      presets: [['react-app', { flow: false, typescript: true }]],
    },
  });
  config.resolve.extensions.push('.ts', '.tsx');
  return config;
},
};

 

storybook webpack은 아래와 같이 사용하면 된다.

const path = require('path');
const root = path.resolve(__dirname, '../');
const alias = {
  '@modules': `${root}/src/modules`,
  '@model': `${root}/src/model`,
  '@hooks': `${root}/src/hooks`,
  '@constants': `${root}/src/constants`,
  '@config': `${root}/src/config`,
  '@api': `${root}/src/api`,
  '@components': `${root}/src/components`,
  '@pages': `${root}/src/pages`,
  '@utils': `${root}/src/utils`,
  '@types': `${root}/src/utils`,
  'sprite': `${root}/src/sprite/scss`,
  '@public': `${root}/public`
}

const commonCssLoaderOptions = {
  importLoaders: 2,
  url: false
};

const cssLoaderOptions = {
  ...commonCssLoaderOptions,
  modules: false
};

const scssLoaderOptions = {
  ...commonCssLoaderOptions,
  modules: true,
  localIdentName: '[local]--[hash:base64:5]'
};

module.exports = {
  stories: ['../stories/**/*.stories.tsx'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
  webpackFinal: async config => {
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      loader: require.resolve('babel-loader'),
      options: {
        presets: [['react-app', { flow: false, typescript: true }]],
      },
    },
    {
      test: /\.(css)$/,
      include: path.resolve(__dirname, '../public/css'),
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader', options: cssLoaderOptions },
      ],
    },
    {
      test: /\.(scss)$/,
      include: path.resolve(__dirname, '../src'),
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader', options: scssLoaderOptions },
        { loader: 'sass-loader' },
      ],
    },
    {
      test: /\.(scss)$/,
      include: path.resolve(__dirname, '../node_modules'),
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader', options: commonCssLoaderOptions },
        { loader: 'sass-loader' },
      ],
    }
    );
    config.resolve.alias = {
      ...config.resolve.alias,
      ...alias
    };

    config.resolve.extensions.push('.ts', '.tsx');
    return config;
  },
};

4. tsconfig에 "rootDir": ["src", "stories"]를 추가한다.

{
  "compilerOptions": {
	...
    "rootDirs": [
      "src",
      "stories"
    ]
  },
  ...
}

펄풱트. 

 

Link를 감싸고 있는 컴포넌트를 사용할 때

TripItem.tsx

import React from 'react';
import { hot } from 'react-hot-loader/root';
import classNames from 'classnames/bind';
import { Link } from 'react-router-dom';
import { ROUTE_PATH } from '@config/routes';
import { ITrip } from '../../types/api';
import Img from '@components/Img';

const style = require('./index.scss');
const cx = classNames.bind(style);

interface IOwnProps {
  tripInfo: ITrip
  layoutType: string
}

const TripItem: React.FC<IOwnProps> = ({ tripInfo, layoutType }) => {
  const layoutClassName = layoutType.toLowerCase();

  return (
    <Link to={`${ROUTE_PATH.DETAIL.url}/${tripInfo.id}`} className={cx('trip_item', layoutClassName)}>
      <div className={cx('inner')}>
        <Img src={tripInfo.imageUrl} useLazyLoad={true} className={cx('trip_image')}/>
        <div className={cx('info')}>
          <strong className={cx('title')}>{tripInfo.title}</strong>
          <p className={cx('date')}>{tripInfo.startDate} ~ {tripInfo.endDate}</p>
          <div>
            <Img src={tripInfo.country.imgUrl} className={cx('country_image')} useLazyLoad={true} />
          </div>
          <p className={cx('expense')}>₩476,708</p>
        </div>
      </div>
    </Link>
  );
};

export default hot(TripItem);

stories/2-Trip.stories.tsx

export const Trip_1 = () => (
    <TripItem
      tripInfo={{
        id:'5ed904eae5a35e8cad4b7a48',
        title:'123',
        memo:'testtest',
        country: {
          id:"390",
          name:"가나",
          en:"Ghana",
          imgUrl:"http://www.0404.go.kr/imgsrc.mofa?atch_file_id=FILE_000000000010504&file_sn=1",
          country:"가나",
          currency: {
            ko:"세디",
            en:"GHS",
            rate:"212.1682"
          }
        },
        imageUrl:'http://static.hubzum.zumst.com/hubzum/2018/10/18/16/189e444ee9114cb9ba0271f6fc09af32_780x0c.jpg',
        startDate:'Wed Jun 03 2020',
        endDate:'Wed Jun 10 2020',
        status:''
      }}
      layoutType={'type_1'}
    />
);

Trip_1.stroy = {
  name: 'type1'
}

아래와 같이 Invariant failed: You should not use <Link> outside a <Router> 에러가 발생한다.

 

해결 방법: <MemoryRouter>로 감싸주면 된다.

import React from 'react';

import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import TripItem from '../src/components/TripItem';
import { MemoryRouter } from 'react-router-dom';

export default {
  title: 'tripItem',
};

export const Trip_1 = () => (
  <MemoryRouter>
    <TripItem
      tripInfo={{
        id:'5ed904eae5a35e8cad4b7a48',
        title:'123',
        memo:'testtest',
        country: {
          id:"390",
          name:"가나",
          en:"Ghana",
          imgUrl:"http://www.0404.go.kr/imgsrc.mofa?atch_file_id=FILE_000000000010504&file_sn=1",
          country:"가나",
          currency: {
            ko:"세디",
            en:"GHS",
            rate:"212.1682"
          }
        },
        imageUrl:'http://static.hubzum.zumst.com/hubzum/2018/10/18/16/189e444ee9114cb9ba0271f6fc09af32_780x0c.jpg',
        startDate:'Wed Jun 03 2020',
        endDate:'Wed Jun 10 2020',
        status:''
      }}
      layoutType={'type_1'}
    />
  </MemoryRouter>
);

Trip_1.stroy = {
  name: 'type1'
}

 

끝~ 이제 사용 방법을 공부해보장

 

참고: https://hyunseob.github.io/2018/01/08/storybook-beginners-guide/

 

Storybook 입문 가이드

페이지 단위의 개발이 이루어지던 과거와 달리 요즘의 프론트엔드 개발은 주로 컴포넌트 단위로 이루어진다. 이 컴포넌트라는 개념은 사용하는 라이브러리나 프레임워크에 따라 구현 방식이 ��

hyunseob.github.io

 

'JavaScript > React.js' 카테고리의 다른 글

CRA + Craco + TypeScript  (0) 2020.11.16
Express + React 배포 설정  (0) 2020.07.29
BrowserRouter에서 path를 직접 접근할 때 발생하는 이슈  (0) 2020.03.13
react에서 lottie 사용하기  (0) 2019.10.17
리액트 LifeCycle  (0) 2019.05.02