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

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

meijin
June 03, 2022

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

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) よかったらフォローしてね ` ` ` `