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

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

7fccfb690a818ed2f3a15fc21d426d5a?s=47 meijin
June 03, 2022

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

7fccfb690a818ed2f3a15fc21d426d5a?s=128

meijin

June 03, 2022
Tweet

More Decks by meijin

Other Decks in Programming

Transcript

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

  2. 自己紹介

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

    でAPI 書いたり、 Nuxt でフロントエンド書いたり、React Native 書い てます ちょうど先週から今週に掛けてExpo42 →45 へのア プデ&EAS Build 移行をやって大量のバグを踏んで ましたw
  4. マナリンクについて①

  5. マナリンクについて② 一言で言うと オンライン家庭教師と生徒のマッチングプラットフォーム を提供することで 日本中・世界中の先生が活躍できる場所を作っています 開発しているプロダクトは オンライン家庭教師特化の検索サイト オンライン指導専用アプリ (React Native)

  6. 今回の発表内容

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

  8. 説明の流れ Storybook とは ストーリーの書き方 React Native アプリへのStorybook 導入 Storybook を動かす

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

    Storybook を導入してみたタイミングのため、運用の知見はまだありません
  10. Storybook とは

  11. Storybook とは? コンポーネントを実際にページに埋め込むこと無く単体で開発・テスト・運用できるようにするツール Storybook があると嬉しいこと コンポーネント単位での動作確認が楽になる ページを実際に開く必要がなくなる バックエンドを起動しなくても確認できる コンポーネント内の条件分岐が可視化される 「ストーリー」

    にコンポーネントの表示パターンを一覧できる 「このパターンの表示忘れていた!」といった改修漏れが防げる
  12. ストーリーの例 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: ' 送信する', }, };
  13. ストーリーの書き方 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 でメタ情報をエクスポートする ストーリーとして書きたいコンポーネント( 必須) や、 ストーリー自身のタイトル( 省略可) を指定
  14. ストーリーの書き方② 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 を渡す( 型安全!) 本例はデフォルトとローディングのパターンを指定
  15. Storybook の dev server を起動すると ブラウザで(localhost:6006 などで) 専用の画面が立ち上がる! 画面左ペインにパターン別にコンポーネントが並んでいる

  16. Storybook の dev server を起動すると② パターンを切り替えると見た目が変わる

  17. もっと詳しいことは 公式ドキュメント 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 です
  18. React Native アプリへの Storybook 導入

  19. React Native アプリへの Storybook 導入の壁 Storybook は基本的にはWeb ブラウザ上で動く React Native

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

    自体はReact アプリケーションを扱っているかのようにセットアップする セットアップコマンド ` ` ` ` npx -p @storybook/cli sb init --type react
  21. 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'], }, }, ],
  22. 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]; ` `
  23. 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 を使いましょう ` ` ` `
  24. 余談: @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, }; ` ` ` ` ` ` ` ` ` ` ` `
  25. 余談②:うまく動かない実例 (当然だが)アラートなどネイティブ独自のUI は再現できない react-navigation の useNavigation を使っているコンポーネントがStorybook 上で表示できていない (自社の環境でのみ再現) https://davidl.fr/blog/react-navigation-object-storybook

    の内容をやってみてもダメだった ` ` ` `
  26. @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 の更新情報が流れているの で、気になる方は引き続きウォッチしていると進展があるかも ` ` ` ` ` `
  27. MSW を使ってネットワークをモックする

  28. 概要 コンポーネントからGET したりPOST したり、ネットワークにアクセスする場合がある 一案としてネットワークへアクセスするコンポーネントと、その結果やハンドラのみProps 経由でやり取り するコンポーネントに切り分けて、後者だけStorybook を使う方法もある もう一案として、ネットワークへのアクセスをモックできるMSW(Mock Service

    Worker) を使って、ネット ワークごとStorybook で表現する方法がある msw-storybook-addon を使う ` `
  29. 今回 MSW を使いたいコンポーネント 検索窓に単語を入れると、先生や科目名がサジェストされる。サジェスト内容はAPI を介してGET される。 コンポーネントの性質上、API へのGET した結果に沿ってサジェストされるところまで動きを見れてナンボ

  30. 今回 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 リクエストを飛ばし、そ の結果をサジェストアイテムにセットしている 余談だがキャンセル処理に注意
  31. 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 ミリ秒遅らせて( リアルに) 入力させる
  32. 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 固有なので覚える
  33. MSW を組み込んだ結果 ユーザーの入力と、それに応じてAPI を叩いてサジェストする様子を、コンポーネント単体でシミュレートで きるようになった!

  34. Scaffdog でチーム運用に乗せる

  35. Story をいちいち書くのは地味に面倒 よく見るのはコンポーネントと同じディレクトリに ComponentName.stories.tsx という名前でストーリ ーを置く いちいちコンポーネントを作るたびに同じような作業をしてストーリーを作るのは地味に面倒 ` `

  36. Scaffdog がおすすめ https://github.com/cats-oss/scaffdog 簡単にいうと・・・ ファイルのひな形を作っておくと、コマンド一発でひな形に沿ったファイルを作成、配置してくれる Markdown でひな形を書く 同時に複数のファイルをひな形に沿って一発生成できる 複数種類のファイルを、同じような形式でチームで統一して作ってもらいたいときに便利!

  37. マークダウンのひな形の例 複数のファイルを見出しで切り分けてひな形を指定できる コンポーネント、Storybook 、(必要なら)jest ファイル... といった感じで一度にまとめて書ける

  38. 実際に使ってみる様子 コマンド叩いて、ディレクトリ選んで、コンポーネント名をEnter 。

  39. 以下のような 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: { }, };
  40. まとめ

  41. まとめ @storybook/addon-react-native-web を使うのが現状はおすすめ ただし、 useNavigation を内部で使っていると動作しない→動いたという企業さんがいたので、バー ジョン等の兼ね合いかも? MSW でAPI アクセスをモックできるのが便利

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