Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React NativeアプリにStorybook CSF3.0を導入しよう

Avatar for meijin meijin
June 03, 2022

React NativeアプリにStorybook CSF3.0を導入しよう

Avatar for meijin

meijin

June 03, 2022
Tweet

More Decks by meijin

Other Decks in Programming

Transcript

  1. 自己紹介 名人 Twitter: @meijin_garden 株式会社NoSchool CTO オンライン家庭教師サービスの「マナリンク」を 開発しています 普段はPM したり、Laravel

    でAPI 書いたり、 Nuxt でフロントエンド書いたり、React Native 書い てます ちょうど先週から今週に掛けてExpo42 →45 へのア プデ&EAS Build 移行をやって大量のバグを踏んで ましたw
  2. 説明の流れ Storybook とは ストーリーの書き方 React Native アプリへのStorybook 導入 Storybook を動かす

    msw(Mock Service Worker) の導入 Scaffdog でチーム運用に乗せる まとめ
  3. 前提 環境 React Native Expo (Managed Workflow ) Native Base

    Storybook を導入してみたタイミングのため、運用の知見はまだありません
  4. ストーリーの例 Button.tsx のストーリーとして Button.stories.tsx の例 ` ` ` ` import

    { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import Button from './index'; export default { component: Button, } as ComponentMeta<typeof Button>; export const Default: ComponentStoryObj<typeof Button> = { args: { children: ' 送信する', }, }; export const IsLoading: ComponentStoryObj<typeof Button> = { args: { isLoading: true, children: ' 送信する', }, };
  5. ストーリーの書き方 import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import Button

    from './index'; export default { component: Button, } as ComponentMeta<typeof Button>; export const Default: ComponentStoryObj<typeof Button> = { args: { children: ' 送信する', }, }; export const IsLoading: ComponentStoryObj<typeof Button> = { args: { isLoading: true, children: ' 送信する', }, }; default export でメタ情報をエクスポートする ストーリーとして書きたいコンポーネント( 必須) や、 ストーリー自身のタイトル( 省略可) を指定
  6. ストーリーの書き方② import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import Button

    from './index'; export default { component: Button, } as ComponentMeta<typeof Button>; export const Default: ComponentStoryObj<typeof Button> = { args: { children: ' 送信する', }, }; export const IsLoading: ComponentStoryObj<typeof Button> = { args: { isLoading: true, children: ' 送信する', }, }; const export でコンポーネントのパターンをエクス ポートする args にコンポーネントのProps を渡す( 型安全!) 本例はデフォルトとローディングのパターンを指定
  7. もっと詳しいことは 公式ドキュメント https://storybook.js.org/docs/react/get-started/introduction ただし、Storybook6.4 からストーリーの書き方 (Component Story Format) が 3.0

    となり大幅に改善されたた め、以下ブログを最初に読み、改善後の書き方を覚えた上で読むべし https://storybook.js.org/blog/component-story-format-3-0/ 本スライドで紹介しているのはCSF3.0 です
  8. React Native アプリへの Storybook 導入の壁 Storybook は基本的にはWeb ブラウザ上で動く React Native

    のコンポーネントはそのままでは動作しない 策は2 つ Storybook でのみ react-native-web としてコンポーネントを扱う @storybook/react-native を使う 順に説明します ` ` ` `
  9. react-native-web としてコンポーネントを扱う① 全体感 Storybook 実行時のみ、 react-native を react-native-web として扱う Storybook

    自体はReact アプリケーションを扱っているかのようにセットアップする セットアップコマンド ` ` ` ` npx -p @storybook/cli sb init --type react
  10. react-native-web としてコンポーネントを扱う② React Native Web addon for Storybook をインストールします .storybook/main.js

    に追記 ` ` yarn add babel-plugin-react-native-web @storybook/addon-react-native-web --dev ` ` addons: [ '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions', // see https://github.com/storybookjs/addon-react-native-web README { name: '@storybook/addon-react-native-web', options: { modulesToTranspile: ['react-native-vector-icons'], babelPlugins: ['@babel/plugin-transform-react-jsx'], }, }, ],
  11. react-native-web としてコンポーネントを扱う③ NativeBase を使っているなど、共通のContextProvider が必要な場合は 同様に生成される .storybook/preview.js も追記する Decorator という、Storybook

    の機能を使っています (https://storybook.js.org/docs/react/writing-stories/decorators) ` ` const withThemeProvider = (Story, context) => { return ( <NativeBaseProvider theme={theme}> <Story {...context} /> </NativeBaseProvider> ); }; export const decorators = [withThemeProvider]; ` `
  12. react-native-web としてコンポーネントを扱う [ まとめ ] @storybook/addon-react-native-web の作者によって以下の記事にまとめられています。 https://levelup.gitconnected.com/introducing-react-native-web-storybook-3c56d48a6508 メリット 最新のAddon

    が利用できるよ Chromatic などのVRT がサポートされているよ 静的ファイルとして簡単に公開できるよ 最新のStorybook の機能が全部使えるよ デメリット react-native-web 未サポートのライブラリは使えないよ ネイティブデバイスと同じ動きは再現できないよ どうしてもネイティブの動きを再現したければ @storybook/react-native を使いましょう ` ` ` `
  13. 余談: @storybook/addon-react-native-web を読む https://github.com/storybookjs/addon-react-native- web/blob/ffe28c7faae2e1fea5e1757826c0c6848a71be7f/src/webpack.ts react-native を react-native-web にエイリアスしたり、 react-native-web

    特有の拡張子の .web.js などをresolve のターゲットに入れたりしてます。 @storybook/addon-react-native-web を使わ なくても、これらの設定を .storybook/main.js に書けばいけるかも。 config.resolve.extensions = [ '.web.js', '.web.jsx', '.web.ts', '.web.tsx', ...config.resolve.extensions, ]; config.resolve.alias = { 'react-native$': 'react-native-web', ...config.resolve.alias, ...userAliases, }; ` ` ` ` ` ` ` ` ` ` ` `
  14. @storybook/react-native を使う ネイティブデバイス上でStorybook を確認できるプラグイン しかし基本的に開発がWeb のStorybook より遅れている 2022 年5 月30

    日時点では v6.0.1-beta.6 が出ているが、本家は v6.5 以上 セットアップ手順は以下 https://github.com/storybookjs/react-native/blob/v6.0.1-beta.6/MANUAL_SETUP.md ・・・らしいけど自分の手元で動かしてもCSF3.0 では動作しませんでした 自分の見解 現状、Storybook を使うのならば、割り切ってreact-native-web Addon を使うのがよさそう Storybook 公式Discord のreact-native チャンネルで @storybook/react-native の更新情報が流れているの で、気になる方は引き続きウォッチしていると進展があるかも ` ` ` ` ` `
  15. 今回 MSW を使いたいコンポーネント ( 一部 ) export const useSuggestField: (props:

    useHooksParams) => useHooksReturn = (props) => { const [value, setValue] = useState(''); const [suggestItems, setSuggestItems] = useDebounce<AutocompleteItem[]>([]); useEffect(() => { let canceled = false; apiClient.teachers.suggests.$get({ query: { keyword: value } }).then((res) => { if (!canceled) { if (res.length === 0) { setSuggestItems([]); } setSuggestItems(res); } }); return () => { canceled = true; }; }, [value, setSuggestItems]); const onInput = useCallback(async (str: string) => { setValue(str); aspida を使って、「/teachers/suggests 」というAPI にValue が変わるたびにGET リクエストを飛ばし、そ の結果をサジェストアイテムにセットしている 余談だがキャンセル処理に注意
  16. MSW を使ったストーリー① export const SearchSubject: ComponentStoryObj<typeof SearchSuggestField> = { args:

    { onSubmit: () => undefined, }, play: async () => { const input = screen.getByLabelText(' 科目名や先生名を入力するテキストフィールド'); await userEvent.type(input, ' 英語', { delay: 200, }); }, parameters: { msw: { handlers: [ rest.get('/teachers/suggests', (req, res, ctx) => { return res( ctx.json([ { target: 'subjects', item_id: 'english', name: ' 英語', }, ] as AutocompleteItem[]), ); play 関数を指定すると、テキストフィールドに文字 を入力させるなどの操作ができる userEvent.type メソッドで「英語」という文字を200 ミリ秒遅らせて( リアルに) 入力させる
  17. MSW を使ったストーリー② export const SearchSubject: ComponentStoryObj<typeof SearchSuggestField> = { args:

    { onSubmit: () => undefined, }, play: async () => { const input = screen.getByLabelText(' 科目名や先生名を入力するテキストフィールド'); await userEvent.type(input, ' 英語', { delay: 200, }); }, parameters: { msw: { handlers: [ rest.get('/teachers/suggests', (req, res, ctx) => { return res( ctx.json([ { target: 'subjects', item_id: 'english', name: ' 英語', }, ] as AutocompleteItem[]), ); 今回の例は、「/teachers/suggests 」というAPI に GET リクエストがあれば、それをIntercept して( 遮っ て) 補完内容をレスポンスする、という意味 細かい書き方はMSW とAddon 固有なので覚える
  18. 以下のような Story が自動生成される import type { ComponentMeta, ComponentStoryObj } from

    '@storybook/react'; import { Button } from './'; export default { component: Button, } as ComponentMeta<typeof Button>; export const Default: ComponentStoryObj<typeof Button> = { args: { }, };
  19. まとめ @storybook/addon-react-native-web を使うのが現状はおすすめ ただし、 useNavigation を内部で使っていると動作しない→動いたという企業さんがいたので、バー ジョン等の兼ね合いかも? MSW でAPI アクセスをモックできるのが便利

    Scaffdog で必要なファイルの自動生成させると運用に乗せやすいかも 告知 オンライン家庭教師マナリンクではエンジニア採用やってます! React Native でオンライン指導アプリを開発したい方はお声がけください Twitter( 名人|マナリンクCTO / @meijin_garden) よかったらフォローしてね ` ` ` `