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. React Native
    アプリに


    Storybook CSF3.0
    を導入しよう
    meijin

    View full-size slide

  2. 自己紹介

    View full-size slide

  3. 自己紹介
    名人
    Twitter: @meijin_garden
    株式会社NoSchool CTO
    オンライン家庭教師サービスの「マナリンク」を

    開発しています
    普段はPM
    したり、Laravel
    でAPI
    書いたり、

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

    View full-size slide

  4. マナリンクについて①

    View full-size slide

  5. マナリンクについて②
    一言で言うと
    オンライン家庭教師と生徒のマッチングプラットフォーム を提供することで

    日本中・世界中の先生が活躍できる場所を作っています
    開発しているプロダクトは
    オンライン家庭教師特化の検索サイト
    オンライン指導専用アプリ
    (React Native)

    View full-size slide

  6. 今回の発表内容

    View full-size slide

  7. React Native
    アプリに


    Storybook CSF3.0
    を導入しよう

    View full-size slide

  8. 説明の流れ
    Storybook
    とは
    ストーリーの書き方
    React Native
    アプリへのStorybook
    導入
    Storybook
    を動かす
    msw(Mock Service Worker)
    の導入
    Scaffdog
    でチーム運用に乗せる
    まとめ

    View full-size slide

  9. 前提
    環境
    React Native
    Expo
    (Managed Workflow

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

    View full-size slide

  10. Storybook
    とは

    View full-size slide

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

    View full-size slide

  12. ストーリーの例
    Button.tsx
    のストーリーとして Button.stories.tsx
    の例
    ` ` ` `
    import { ComponentMeta, ComponentStoryObj } from '@storybook/react';

    import Button from './index';

    export default {

    component: Button,

    } as ComponentMeta;

    export const Default: ComponentStoryObj = {

    args: {

    children: '
    送信する',

    },

    };

    export const IsLoading: ComponentStoryObj = {

    args: {

    isLoading: true,

    children: '
    送信する',

    },

    };

    View full-size slide

  13. ストーリーの書き方
    import { ComponentMeta, ComponentStoryObj } from '@storybook/react';

    import Button from './index';

    export default {

    component: Button,

    } as ComponentMeta;

    export const Default: ComponentStoryObj = {

    args: {

    children: '
    送信する',

    },

    };

    export const IsLoading: ComponentStoryObj = {

    args: {

    isLoading: true,

    children: '
    送信する',

    },

    };
    default export
    でメタ情報をエクスポートする
    ストーリーとして書きたいコンポーネント(
    必須)
    や、

    ストーリー自身のタイトル(
    省略可)
    を指定

    View full-size slide

  14. ストーリーの書き方②
    import { ComponentMeta, ComponentStoryObj } from '@storybook/react';

    import Button from './index';

    export default {

    component: Button,

    } as ComponentMeta;

    export const Default: ComponentStoryObj = {

    args: {

    children: '
    送信する',

    },

    };

    export const IsLoading: ComponentStoryObj = {

    args: {

    isLoading: true,

    children: '
    送信する',

    },

    };
    const export
    でコンポーネントのパターンをエクス
    ポートする
    args
    にコンポーネントのProps
    を渡す(
    型安全!)
    本例はデフォルトとローディングのパターンを指定

    View full-size slide

  15. Storybook

    dev server
    を起動すると
    ブラウザで(localhost:6006
    などで)
    専用の画面が立ち上がる!
    画面左ペインにパターン別にコンポーネントが並んでいる

    View full-size slide

  16. Storybook

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

    View full-size slide

  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
    です

    View full-size slide

  18. React Native
    アプリへの
    Storybook
    導入

    View full-size slide

  19. React Native
    アプリへの
    Storybook
    導入の壁
    Storybook
    は基本的にはWeb
    ブラウザ上で動く
    React Native
    のコンポーネントはそのままでは動作しない
    策は2

    Storybook
    でのみ react-native-web
    としてコンポーネントを扱う
    @storybook/react-native
    を使う
    順に説明します
    ` `
    ` `

    View full-size slide

  20. react-native-web
    としてコンポーネントを扱う①
    全体感
    Storybook
    実行時のみ、 react-native
    を react-native-web
    として扱う
    Storybook
    自体はReact
    アプリケーションを扱っているかのようにセットアップする
    セットアップコマンド
    ` ` ` `
    npx -p @storybook/cli sb init --type react

    View full-size slide

  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'],

    },

    },

    ],

    View full-size slide

  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 (







    );

    };

    export const decorators = [withThemeProvider];
    ` `

    View full-size slide

  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
    を使いましょう
    ` `
    ` `

    View full-size slide

  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,

    };
    ` ` ` ` ` `
    ` ` ` `
    ` `

    View full-size slide

  25. 余談②:うまく動かない実例
    (当然だが)アラートなどネイティブ独自のUI
    は再現できない
    react-navigation
    の useNavigation
    を使っているコンポーネントがStorybook
    上で表示できていない
    (自社の環境でのみ再現)
    https://davidl.fr/blog/react-navigation-object-storybook
    の内容をやってみてもダメだった
    ` ` ` `

    View full-size slide

  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
    の更新情報が流れているの
    で、気になる方は引き続きウォッチしていると進展があるかも
    ` ` ` `
    ` `

    View full-size slide

  27. MSW
    を使ってネットワークをモックする

    View full-size slide

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

    View full-size slide

  29. 今回
    MSW
    を使いたいコンポーネント
    検索窓に単語を入れると、先生や科目名がサジェストされる。サジェスト内容はAPI
    を介してGET
    される。

    コンポーネントの性質上、API
    へのGET
    した結果に沿ってサジェストされるところまで動きを見れてナンボ

    View full-size slide

  30. 今回
    MSW
    を使いたいコンポーネント
    (
    一部
    )
    export const useSuggestField: (props: useHooksParams) => useHooksReturn = (props) => {

    const [value, setValue] = useState('');

    const [suggestItems, setSuggestItems] = useDebounce([]);

    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
    リクエストを飛ばし、そ
    の結果をサジェストアイテムにセットしている
    余談だがキャンセル処理に注意

    View full-size slide

  31. MSW
    を使ったストーリー①
    export const SearchSubject: ComponentStoryObj = {

    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
    ミリ秒遅らせて(
    リアルに)
    入力させる

    View full-size slide

  32. MSW
    を使ったストーリー②
    export const SearchSubject: ComponentStoryObj = {

    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
    固有なので覚える

    View full-size slide

  33. MSW
    を組み込んだ結果
    ユーザーの入力と、それに応じてAPI
    を叩いてサジェストする様子を、コンポーネント単体でシミュレートで
    きるようになった!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. 以下のような
    Story
    が自動生成される
    import type { ComponentMeta, ComponentStoryObj } from '@storybook/react';

    import { Button } from './';

    export default {

    component: Button,

    } as ComponentMeta;

    export const Default: ComponentStoryObj = {

    args: {

    },

    };

    View full-size slide

  40. まとめ
    @storybook/addon-react-native-web
    を使うのが現状はおすすめ
    ただし、 useNavigation
    を内部で使っていると動作しない→動いたという企業さんがいたので、バー
    ジョン等の兼ね合いかも?
    MSW
    でAPI
    アクセスをモックできるのが便利
    Scaffdog
    で必要なファイルの自動生成させると運用に乗せやすいかも
    告知
    オンライン家庭教師マナリンクではエンジニア採用やってます!

    React Native
    でオンライン指導アプリを開発したい方はお声がけください
    Twitter(
    名人|マナリンクCTO / @meijin_garden)
    よかったらフォローしてね
    ` `
    ` `

    View full-size slide