アプリを開発する前に予め、開発の全体像を把握するためにプロトタイプを作成します。
プロトタイピングツールで作成したUIは、実際のアプリ開発にも流用できるため効率的です。
今回は簡単な写真共有Webブラウザアプリ
を制作すると仮定して、
- プロトタイピングツール
Figma
でプロトタイプ作成 -
Reactコンポーネント
への落とし込み -
Storybook
での管理
という一連の流れを実際に実装しながらFigma
やStorybook
を導入することでどういったメリットがあるのか・どういったことができるのかの検証を行います。
作成したプロトタイプの全体像
最初にどんなアプリなのかをスクリーンショットで提示してから、詳細な解説を進めていきます。
基本的なアプリに最低限必要な認証
・一覧表示
・登録
・編集
機能をプロトタイピングで再現しました。
マテリアルデザイン(Material-ui)をベースとしています。
ログイン画面
- ユーザーサインイン
- ユーザー新規登録
- パスワードリセット
アルバム一覧・投稿画面
- アルバム一覧表示
- アルバム投稿
- アルバム編集
- アルバム削除
なぜFigmaを使うのか
Figma公式サイト
https://www.figma.com/
プロトタイピングツールにはSketch
・AdobeXD
など様々な種類がありますが、今回Figma
を選択したのは以下の理由からです。
- ブラウザからアクセスしサインインするだけで、ほとんどの機能を無料で使える
- コンポーネント・スタイルの管理が楽そう
- 機能面でSketchやXDと遜色なさそう
- リアルタイムコラボレーションを試してみたかった
- 話題の
web assembly
で記述されているwebアプリの操作性を実際に体験してみたかった
Figmaで作るスタイルガイド
若干我流かもしれませんがAtomic Design
を意識して、スタイルガイドを作成してみました。後ほど、Material-ui
でコードを組む予定だったので、フォント・カラーなどの命名・パターンは極力Material-uiデフォルトのものを使用し齟齬が出ないようにしました。
Figmaではカラー・フォントはローカルスタイルとして定義することができ、定義されたスタイルをボタンやフォームなどに適用すれば、同一のスタイルを一括で変更したりと変更に強いデータを作成することが可能です。
フォント
Material-uiにもともと定義されているタイポグラフィ+タイトルのフォントを定義。
カラー
- ベースカラー → Material-uiのオリジナルカラー
grey
のカラーパターン - メインカラー → 独自のネイビー系の配色
dark
・main
・light
の3パターン - アクセントカラー → 独自のオレンジ系の配色
dark
・main
・light
の3パターン
アイコン・ボタン・フォーム
Figma便利機能
Figmaは奥が深く、いろいろと便利機能があるのですが、私が利用するなかで特に便利だと思った機能について解説を行います。
コンポーネント
図形やテキストなどのパーツをまとめたものをコンポーネントとして定義することができます。
画面上部メニューにある菱形アイコンをクリックするか、パーツを全て選択右クリックCreate Component
で簡単にコンポーネントを作成できます。
コンポーネントはコピーすることで、インスタンスを生成することができ、コピー元コンポーネント(マスターコンポーネント)の変更がリアルタイムで反映されるようになります。
生成されたインスタンスはパーツの位置のみは固定で、以下項目についてはオーバーライドして独自のコンポーネントを作成することが可能です。
- カラー
- 画像
- テキストの内容
- コンポーネントのサイズ
コンストレイント
上記のコンポーネント機能で、インスタンスを作成しコンポーネントのサイズ感を変更したい。しかし、コンポーネントのサイズを変更すると図形の比率や配置が変更されてしまった。そんなときに使えるのがコンストレイント機能です。コンストレイント機能を使うと、「左上」「右上」「左下」「右下」「中央」の中から、位置固定の基準位置を設定することができます。
コンポーネントの整列
プロトタイプを作成していると、複数のコンポーネント間を一発で調整したい時があるはずです。Figmaなら整列ボタンを使わなくても直感的にドラックアンドドロップで調整できるので作業効率が上がります。Illustratorやphotoshopではこんな機能なかったのですごく便利に感じました。
適当に配置した、オブジェクトをいい感じに整列してくれる「Tidy Up」もそこそこ便利です。整列ボタンから選択できます。
画像の一括配置
画像も複数画像を選択してサクサク配置することができます。アプリのイメージを変えたいなと思った時も10秒とかからず変更することができます。ただ、Sketchのようにテキストの一括流し込みができないのは残念です。
プロトタイピング
Figmaでは画面のレイアウトを作成したら、そのままプロトタイピングも実装できます。サイドバーメニューからPrototype
タブを選択し、あとはトリガーとなるボタンまたはフォームから遷移先のフレームに以下画像のように動線を結べば「クリックしたら画面遷移」や「ホバーしたらコンポーネント表示」などを実現させることができます。
アニメーションについては複雑な動作はできませんが、プロトタイピングの段階では画面遷移だけわかれば良いということがほとんどだと思うので私は十分だと思いました。フェードイン・アウトやムーブイン・アウトなど基本的動作は普通にできます。
コラボレーション
Figmaの一番の強みコラボレーション機能についてです。今回のアプリ制作は1人で作業していたためあまり効力を発揮しませんでしたが、試しに2台PCと2アカウントを用意してコラボレーション機能を試してみました。タイムラグが発生して使いにくいのではないかと思っていましたが、Google Documentのようにデザイン変更はリアルタイムで反映されストレスなく作業を行うことができました。何より、ファイルの受け渡しなく同時平行で複数人が作業・コメントできるのは作業効率や管理コスト面で相当便利な機能だと思います。
なぜStoryBookを使うのか
StoryBook公式サイト
https://storybook.js.org/
StoryBookはUI Component 管理ツール
という位置づけです。スタイルガイドを作成するだけなら、上記で解説したFigma
でも可能です。しかし、実際運用・保守の観点で考えるとデザイン改修の際にFigmaファイルを修正して、その上でアプリのコードを修正するとなると2度手間になってしまいます。Storybook
を利用するとアプリのコードをそのままスタイルガイドにできるので、スタイルガイドを修正する=アプリのコードを修正するということになり「アプリ側のコードだけしか修正されずスタイルガイドの意味がなくなってしまった」というリスクを防ぐことが可能になります。
Figmaはベータ版の段階でざっくりとスタイルガイドを作成する、ある程度案が固まって実際にコードを書き始めたらStorybookでしっかりとスタイルガイドを作り込んで運用するといった感じになると思います。StorybookはFigmaのようにGUIベースではなくコードベースなので、それなりに学習コストもかかりますが、作成したコンポーネントはそのままアプリで使えますのでスタイルガイド作成作業が全く無駄になってしまうということはないと思います。
Storybook for Reactの導入
Storybookを導入したいreactアプリケーションのプロジェクトルートで以下コマンドを入力すると自動でStorybookのsettingを行ってくれます。
npx -p @storybook/cli sb init --type react
以下コマンドでStorybookを起動するとブラウザでスタイルガイドが開きます。
yarn storybook
typescript
を使用する場合は、まず.storybook
配下の2つのファイルに変更を加えます。
(ファイルが存在しなければ追加します)
module.exports = ({ config }) => {
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
presets: [['react-app', { flow: false, typescript: true }]],
},
},
{
loader: require.resolve('react-docgen-typescript-loader'),
},
],
});
config.resolve.extensions.push('.ts', '.tsx');
return config;
};
import { configure } from '@storybook/react';
// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.tsx$/), module);
次にプロジェクトルートに存在するtsconfig.json
ファイルを書き換えます
{
"compilerOptions": {
"outDir": "build/lib",
"module": "commonjs",
"target": "es5",
"lib": ["es5", "es6", "es7", "es2017", "dom"],
"sourceMap": true,
"allowJs": false,
"jsx": "react",
"moduleResolution": "node",
"rootDirs": ["src", "stories"],
"baseUrl": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build", "scripts"]
}
Storybookアドオン
Storybook単体ではスタイルガイドの機能としては不十分なので、アドオンを追加していきます。
アドオンの追加は基本的には以下の手順です。
アドオンのインストールを行い
npm i -D @storybook/{ アドオン名 }
addons.jsファイルに以下コードを記述
import '@storybook/{ アドオン名 }/register';
addon-knobs
スタイルガイドの対象コンポーネントにprops
を渡して、コンポーネントの状態を変化させることができます。GUIで簡単に確認できるので、非エンジニアでもコンポーネントのデザインチェックを行うことができます。
import React from 'react';
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import StarBorderIcon from '@material-ui/icons/StarBorder';
import { storiesOf } from '@storybook/react';
const colorOptions: any = {
primary: 'primary',
secondary: 'secondary',
inherit: 'inherit',
default: 'default',
};
const sizeOptions: any = {
small: 'small',
medium: 'medium',
large: 'large',
};
const ButtonComponent: React.FC = () => {
return (
<List>
<ListItem>
<Button
color={select('Color', colorOptions, colorOptions.primary)}
size={select('Size', sizeOptions, sizeOptions.medium)}
disabled={boolean('disabled', false)}
>
{text('Label', 'Menu Button')}
</Button>
</ListItem>
<ListItem>
<IconButton
color={select('Color', colorOptions, colorOptions.primary)}
>
<StarBorderIcon />
</IconButton>
</ListItem>
</List>
);
};
storiesOf('./Button', module)
.addDecorator(withKnobs)
.add('./Button', () => <ButtonComponent />);
addon-actions
import React from 'react';
import { action } from '@storybook/addon-actions';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import StarBorderIcon from '@material-ui/icons/StarBorder';
import { storiesOf } from '@storybook/react';
const ButtonComponent: React.FC = () => {
return (
<List>
<ListItem>
<Button
onClick={action('button-click')}
>
Menu Button
</Button>
</ListItem>
<ListItem>
<IconButton
onClick={action('icon-button-click')}
>
<StarBorderIcon />
</IconButton>
</ListItem>
</List>
);
};
storiesOf('./Button', module)
.add('./Button', () => <ButtonComponent />);
addon-docs
addon-info
の上位互換のアドオンです。markdown
を使ったドキュメントを作ったり、MDX
形式でスタイルガイドを作成したりできるようです。
導入にはnpmモジュールのインストールの他に一手間かける必要があります。
まず、.storybook
配下にpresets.js
を作成し、以下コードを記述します。
module.exports = ['@storybook/addon-docs/react/preset'];
mdx
を使いたい場合config.js
を書き換えます
configure(require.context('../stories', true, /\.stories\.(tsx|mdx)$/), module);
以下のように.mdx
形式ファイルはmarkdown
が使えるため、簡単にきれいなドキュメントを作成できます。
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { ButtonComponent } from './Button'; //reactコンポーネントを読み込み
<Meta title="MDX|ButtonComponent" component={ButtonComponent} />
# ButtonComponent
With `MDX` we can define a story for `ButtonComponent` right in the middle of our
markdown documentation.
<Preview>
<Story name="ButtonComponent">
<ButtonComponent />
</Story>
</Preview>
| TH | TH |
| ---- | ---- |
| TD | TD |
| TD | TD |
addon-storyshots
addon-storyshots
を使えば、jestと組合あせてStorybookに登録されているコンポーネントのテストを行うことができます。Storybookに登録されているコンポーネントに何かしらの変更を加えるとエラーとして検出され異常をすぐに検知することができます。
こちらもaddon-docs
同様、導入にはnpmモジュールのインストールの他に一手間かける必要があります。
ただ、今回はaddon.js
へのregister記述は必要ありません。
まず、追加で以下のモジュールをインストールします。jestスナップショットを行うために必要です。
npm i -D require-context.macro react-test-renderer
次にconfig.js
を書き換えます
import { configure } from '@storybook/react';
import requireContext from 'require-context.macro';
// automatically import all files ending in *.stories.js
const req = requireContext('../stories', true, /\.stories\.(tsx|mdx)$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
最後にjestが検知されるパス(おそらくデフォルトは.src/
)にstoryshots.test.tsx
を作成し、以下のコードを記述します。
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();
jestテストを実行すると、コンポーネントテストが行われ、src配下__snapshots__
フォルダ内にスナップショットが記録されます。
yarn test
PASS src/storyshots.test.tsx
Storyshots
./Button
✓ ./Button (64ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: 3.563s
Ran all test suites.
少しでも変更するとエラーになります。スタイルガイドに変更を加えたい場合は、__snapshots__
を一度削除してjestテストを再度行うことで、変更したコンポーネントが正しいものとして登録されます。
FAIL src/storyshots.test.tsx
Storyshots
./Button
✕ ./Button (57ms)
● Storyshots › ./Button › ./Button
expect(received).toMatchSnapshot()
Snapshot name: `Storyshots ./Button ./Button 1`
- Snapshot
+ Received
@@ -23,11 +23,11 @@
type="button"
>
<span
className="MuiButton-label"
>
- Menu Button
+ Menu But
</span> </button>
</li>
<li
className="MuiListItem-root MuiListItem-gutters"
at match (node_modules/@storybook/addon-storyshots/dist/test-bodies.js:27:20)
at node_modules/@storybook/addon-storyshots/dist/test-bodies.js:39:10
at Object.<anonymous> (node_modules/@storybook/addon-storyshots/dist/api/snapshotsTestsTemplate.js:44:33)
› 1 snapshot failed.
Snapshot Summary
› 1 snapshot failed from 1 test suite. Inspect your code changes or press `
u` to update them.
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 1 failed, 1 total
Time: 4.575s
Ran all test suites.
まとめ
プロトタイピングやスタイルガイド作成は正直面倒です。ですが、全体イメージ・根底ルールをしっかりと定めておくと、改修フェーズで圧倒的に手戻りが少なく楽をすることができます。また、「Storybookに乗せるためにReactのコードをきれいに書かなくては」という衝動にもかられ、コードを書いていく上でもガイドラインがあるとないとでは全く意識が変わってきます。私自身まだ手探り状態なところではありますが、今回の学びを業務にも活かしていこうと思います。
ちなみに今回プロトタイプで作った、写真共有Webブラウザアプリ
をReactで実装する過程を次回以降のアドベントカレンダーの投稿で記述するつもりです。