[React-Native] Storybook 구현

이명제·2023년 6월 14일
post-thumbnail

구현 방법


  1. @storybook/react-native 라이브러리를 이용해서 앱에서 Storybook 구현

    (문서대로 할 경우, 정상적으로 구현 됩니다)

  2. @storybook/react & @storybook/addon-react-native-web 라이브러리를 이용해서 웹사이트에서 Storybook 구현

    (해당 방법은 제대로 된 구현법을 찾을 수 없으며, 여러 문서들을 짜집기해서 구현을 시도했지만 작동하지 않는다. 예전 버전인 v5 자료만 보일뿐이며 작동이 가능한지조차 의문)

→ 위 같은 이유로 해당 문서에서는 1번 방법을 설명하겠습니다.

시작


storybook - react native 설치

npx sb init --type react_native

// "npx sb init --type react_native" 이후에 생성되는 "storybook-generate" scripts
// storybook.requires.js 파일을 생성해준다
yarn storybook-generate

환경 구성

  1. .storybook

  1. App.tsx

    ...
    
    // 해당 코드를 주석처리하고
    // export default App;
    
    // 해당 코드를 넣어준다
    export { default } from './.storybook';
  2. metro.config.js

    module.exports = {
      /* existing config */
      resolver: {
        resolverMainFields: ['sbmodern', 'react-native', 'browser', 'main'],
      },
    };
  3. main.js

    module.exports = {
      stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
      addons: [
        '@storybook/addon-ondevice-controls',
        '@storybook/addon-ondevice-actions',
        '@storybook/addon-essentials',
        '@storybook/addon-styling',
      ],
    };
  1. storybook.requires.js

    /* do not change this file, it is auto generated by storybook. */
    import {
      configure,
      addDecorator,
      addParameters,
      addArgsEnhancer,
      clearDecorators,
    } from '@storybook/react-native';
    import { argsEnhancers } from '@storybook/addon-actions/dist/modern/preset/addArgs';
    import { decorators, parameters } from './preview';
    import '@storybook/addon-ondevice-controls/register';
    import '@storybook/addon-ondevice-actions/register';
    
    global.STORIES = [
      {
        titlePrefix: '',
        directory: './.storybook/stories',
        files: '**/*.stories.?(ts|tsx|js|jsx)',
        importPathMatcher:
          '^\\.[\\\\/](?:\\.storybook\\/stories(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(?:ts|tsx|js|jsx)?)$',
      },
    ];
    
    if (decorators) {
      if (__DEV__) {
        // stops the warning from showing on every HMR
        require('react-native').LogBox.ignoreLogs([
          '`clearDecorators` is deprecated and will be removed in Storybook 7.0',
        ]);
      }
      // workaround for global decorators getting infinitely applied on HMR, see https://github.com/storybookjs/react-native/issues/185
      clearDecorators();
      decorators.forEach((decorator) => addDecorator(decorator));
    }
    
    if (parameters) {
      addParameters(parameters);
    }
    
    try {
      argsEnhancers.forEach((enhancer) => addArgsEnhancer(enhancer));
    } catch {}
    
    const getStories = () => {
      return {
        './.storybook/stories/Button.stories.js': require('./stories/Button.stories.js'),
      };
    };
    
    configure(getStories, module, false);

사용법


  1. 예시에 사용될 컴포넌트 기본 구성 (SomeComponent.tsx)

    // SomeComponent
    interface DefaultProps {
    	text: string;
    	isDisabled: boolean;
    	onPress: () => void;
    };
    
    const SomeComponent = ({ text, isDisabled, onPress }: DefaultProps) => {
    	return (
    		...
    	)
    }
  1. 기본 사용법

    import SomeComponent from 'components/SomeComponent';
    
    ...
    export default {
      title: '카테고리 제목',
      component: SomeComponent,
    	args: {
    		text: 'text',
    		isDisabled: false,
    		onPress: () => {},
    	},
      decorators: [
        (Story) => (
    	     <View>
    	       <Story />
           </View>
        ),
      ],
    };
    
    export const Default = {
      args: {},
    };
  1. 하위탭 생성 (Disabled)

    ...
    export default {
      title: '카테고리 제목',
      component: SomeComponent,
    	args: {
    		text: 'text',
    		isDisabled: false,
    		onPress: () => {},
    	},
      decorators: [
        (Story) => (
    	     <View>
    	       <Story />
           </View>
        ),
      ],
    };
    
    export const Default = {
      args: {},
    };
    
    export const Disabled = {
    	args: {
    		isDisabled: true;
    	}
    };
  1. 1개의 탭에 여러 컴포넌트 생성

    ...
    export default {
      title: '카테고리 제목',
      component: (args) => {
    		return (
    			<>
    				<Text>기본</Text>
    				<SomeComponent {...args} />
    				<Text>비활성화</Text>
    				<SomeComponent {...args} isDisabled={true} />
    			</>
    		)
    	},
    	args: {
    		text: 'text',
    		isDisabled: false,
    		onPress: () => {},
    	},
      decorators: [
        (Story) => (
    	     <View>
    	       <Story />
           </View>
        ),
      ],
    };
    
    export const Default = {
      args: {},
    };
  1. styled-components와 함께 사용법

    import { ThemeProvider } from 'styled-components/native';
    import theme from 'styles/theme';
    
    export default {
    	...
      decorators: [
        (Story) => (
    			<ThemeProvider theme={theme}>
    		    <View>
    		      <Story />
    	      </View>
    			</ThemeProvider>
        ),
      ],
    };
    
    ...

참고사항


  1. elderfo/react-native-storybook/loader 라이브러리
    • storybook.requires.js 에서 작성한 스토리 파일을 자동으로 생성해주는 유용한 라이브러리이지만 현재 v6.5 버전 기준으로 파일을 불러오는 양식이 맞지않아 사용할 수 없다.
    • 해당 라이브러리 최근 개발이 안되고 있음.
  2. https://www.npmjs.com/package/@storybook/react-native-server 라이브러리
    • RN에서 Storybook을 서버 구동시키는 라이브러리인데 웹사이트에서 볼 수 있다.
    • 하지만 웹사이트에서 볼 수 있다고해서 만들어진 컴포넌트까지 볼 수 있는게 아니라, 스토리 탭만 볼 수 있으므로 사용해야할 필요성을 느끼지 못했다.

후기


React환경에서 Storybook구현법과 생태계는 잘 갖춰져 있지만,
React Native에서 Storybook의 구현은 공식문서에서도 언급했듯이 React와 비교해서 부수적인 기능과 편의성이 떨어진다는 것을 절실히 느꼈다.

0개의 댓글