36.4K Views
January 27, 22
スライド概要
StoryBook駆動で 新規システムの開発を試してみた 虎の穴ラボ 古賀 Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 1
アジェンダ 1. 自己紹介 2. StoryBookとは? 3. インストール方法 4. StoryBook駆動開発の進めかた 5. メリット 6. まとめ Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 2
1. 自己紹介 出身地は佐賀です。三重県から地方勤務で虎の穴ラボ株式会社の通販チームにいます。 (2020年12月に入社、二児の父です。) フロントエンドは得意で、バックエンドのぼちぼちです。 推し ・南條愛乃(FripSideのVocal、とある科学の超電磁砲のOPとか) ・Node.js/Java ・Nuxt.js/Tailwind CSS Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 3
2. StoryBookとは? フロントエンドエンジニア向けのオープンソースのツールです。 👉 1. コンポーネント駆動型の UI を高速に開発する 👉 2. UI コンポーネントとページを分離して、開発できる 👉 3. UI の開発、テスト、およびドキュメント化を合理化できる 公式サイトより https://storybook.js.org/ Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 4
インストールは簡単です! Nuxt.js ならすぐに使い始められます! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 5
2. インストール方法 Nuxt.js なら nuxt/storybook をインストールすると、 設定無しで、簡単に既存のNuxt.jsプロジェクトに導入できます (素のVueやReactは公式通りのインストールができて簡単です) 公式サイト https://storybook.nuxtjs.org/ 詳しくは、最近作ったQiitaブログで! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 6
Step1. npm インストール @nuxtjs/storybook、postcssをインストール Nuxt.js ならすぐに使い始められます! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 7
Step2. .gitignoreに追記 Nuxt.js ならすぐに使い始められます! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 8
Step3. nuxt.config.jsに追記 Nuxt.js ならすぐに使い始められます! 後は、npx nuxt storybookで起動! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 9
3ステップで完了🎊 nuxt/storybook が無い時は手順が多くて大変でした、、、 Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 10
3. StoryBook駆動開発の進めかた 👉 1. StroyBookで画面イメージを作る(ほぼ静的な HTML) 👉 2. 画面仕様を作る(StoryBook に Markdown で書く or スプレッドシートに貼る) 👉 3. チーム内で画面仕様のレビュー 👉 4. コンポーネントを整理・共通化する(storybook で動かしながら) 👉 5. バックエンドとの結合部分(ロジック)を書く(StoryBook に FetchAPI のモックを書く) 👉 6. Jest でテストケースを作る Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 11
Markdownで仕様を書ける! 🎊 Markdownで書けると捗りますよね、Documentation As Code 🙌 Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 12
3-2. StoryBook に Markdown で仕様を書ける! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 13
3-2. StoryBook に Markdown で仕様を書ける! 👉 使用例をコピーできる Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 14
3-2. StoryBook に Markdown で仕様を書ける! 👉 コンポーネントの属性の一覧を生成してくれる Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 15
3-2. StoryBook に Markdown で仕様を書ける! 👉 Markdown で仕様が書ける Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 16
3-2. StoryBook に Markdown で仕様を書ける! 👉 Markdown で仕様が書ける Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 17
3-2. StoryBook に Markdown で仕様を書ける! 👉 nuxt.config.js に追記が必要! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 18
Fetch APIのモックを書ける! 🎊 APIが無くても開発進められる! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 19
3-5. StoryBook に API のモックを書ける! 👉 下記のモックを書くと・・・ Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 20
3-5. StoryBook に API のモックを書ける! 👉 FetchAPI(axios)の結果をモックに差し替えられます Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 21
モックのインストールが必要で す 今回は storybook-addon-mock を使います Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 22
Step1. npm インストール Nuxt.js ならすぐに使い始められます! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 23
Step2. nuxt.config.js に追記 Nuxt.js ならすぐに使い始められます! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 24
4. 得られるメリット 👉 1. ドキュメント作成と並行して開発が進められる ➡ その後の開発が楽に! 👉 2. 仕様が実際の画面で気軽に確認できる ➡ 実際の動きが誰でも(他の開発者、利用者など)想像しやすい! 👉 3. バックエンドが無くても開発が進められる(無いこと前提に進める) ➡ スケジュール調整が楽になる! Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 25
全体の開発工数を抑えられる 🎊 設計工数↑開発工数↓↓ (実際の動きが想像しやすいので、仕様ミスが減る) (バックエンドの進捗を気にせずに、コーディングが進められる) Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 26
仕様の確認も楽に 🎊 1. 〜画面で検索して〜エラーになったら、どうなったっけ?的な疑問も 2. 再利用する既存のコンポーネントを試しに動かしたい Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 27
これらが全てコードに、 🎊 スプレッドシートの管理の削減! (Documentation as Code へ、また一歩) Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 28
まとめ StoryBook駆動で新規システムの開発をやってみました。 今のところ、よく言われるStoryBookの陳腐化的なデメリットは感じて無いです。 (スプレッドシートよりは良い感じ、Vue+StoryBookの情報が少なくてたまに詰まるのが困る。 JSX使いたい。) 全体の開発工数を下げつつ、コンポーネントの管理も楽になるので、 よろしければ、お試しください😉 (最後のスライド以降に、利用したソースコードを記載) Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 29
ご静聴、ありがとうございました Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 30
Appendix Copyright (C) 2021 Toranoana Inc. All Rights Reserved. 31
<!-- components/molcules/TextboxLabel.vue -->
<template>
<div class="tw-relative tw-mb-4">
<label :for="id" class="tw-leading-7 tw-text-sm tw-text-gray-600">
<slot></slot>
</label>
<atoms-html-textbox
class="textbox"
:id="id"
:value="value"
:type="type"
:placeholder="placeholder"
:disabled="disabled"
@input="emitEvent($event, 'input')"
@search="emitEvent($event, 'search')"
@enter="emitEvent($event, 'enter')"
>
</atoms-html-textbox>
</div>
</template>
<script>
export default {
props: {
id: { type: String, required: false, default: '' },
type: { type: String, required: false, default: 'text' },
value: { type: String, required: false, default: '' },
placeholder: { type: String, required: false, default: '' },
disabled: { type: Boolean, required: false, default: false },
},
methods: {
emitEvent(eventValue, eventName) {
this.$emit(eventName, eventValue)
},
},
}
</script>
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
32
// components/molcules/TextboxLabel.stories.js
export default {
title: 'molcules/TextboxLabelMdx',
}
export const Basic = ({}) => ({
template:
'<molcules-textbox-label :type="type" :value="value">Basic</molcules-textbox-label>',
props: {
type: {
default: 'text',
},
value: {
default: 'Basic',
},
},
})
Basic.argTypes = {
type: {
control: {
type: 'select',
options: ['text', 'search', 'password'],
},
defaultValue: 'text',
},
value: {
control: 'text',
defaultValue: '',
},
}
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
33
<!-- components/molcules/TextboxLabel.stories.mdx -->
import { Meta, Story, Props } from '@storybook/addon-docs/blocks'
import { action } from '@storybook/addon-actions'
import MolculesTextboxLabel from './TextboxLabel'
<Meta title="molcules/TextboxLabelMdx" component={MolculesTextboxLabel} />
# This is an awesome button
Some **markdown** description, or whatever you want.
## Example
サンプル
<Story name="Basic" args={{ type: 'text', value: 'Basic' }}>
{{
template:
'<molcules-textbox-label :type="type" :value="value">Basic</molcules-textbox-label>',
props: {
type: {
default: 'text',
},
value: {
default: 'Basic',
},
},
}}
</Story>
```html
<molcules-textbox-label :type="text" :value="Basic" />
```
## Props
<Props of={MolculesTextboxLabel} />
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
34
<!-- components/atoms/html/Textbox.vue -->
<template>
<input
:id="id"
ref="textbox"
:value="value"
:type="type"
:placeholder="placeholder"
:disabled="disabled"
:required="required"
@input="$emit('input', $event.target.value)"
@search="$emit('search', $event.target.value)"
@keydown.enter="$emit('enter', $event.target.value)"
/>
</template>
<script>
export default {
props: {
id: { type: String, required: false, default: '' },
type: { type: String, required: false, default: 'text' },
value: { type: String, required: false, default: '' },
placeholder: { type: String, required: false, default: '' },
disabled: { type: Boolean, required: false, default: false },
required: { type: Boolean, required: false, default: false },
},
}
</script>
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
35
<!-- components/molcules/el/RadioGroupRadioListLabel.vue -->
<template>
<atoms-el-col :span="span">
<div class="tw-text-xs tw-text-gray-600"><slot></slot></div>
<atoms-el-radio-group
ref="atoms-el-radio-group"
:value="value"
:disabled="disabled"
@input="input"
@change="change"
>
<atoms-el-radio
v-if="isVisibleAll"
:label="$constants.SELECT_NUMBER_ALL_VALUE"
>全て</atoms-el-radio
>
<atoms-el-radio
v-for="option in options"
:key="option.key"
:label="option.key"
>{{ option.label }}</atoms-el-radio
>
</atoms-el-radio-group>
</atoms-el-col>
</template>
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
<script>
export default {
props: {
isVisibleAll: { type: Boolean, required: false, default: true },
value: {
type: [String, Number, Boolean],
required: false,
default: undefined,
},
disabled: { type: Boolean, required: false, default: false },
span: { type: Number, required: false, default: null },
options: {
type: [Array, Object],
required: false,
default: () => {
return []
},
},
},
methods: {
input(value) {
this.$emit('input', value)
},
change(value) {
this.$emit('change', value)
},
},
}
</script>
36
// components/molcules/el/RadioGroupRadioListLabel.stories.js
import RadioGroupRadioListLabel from './RadioGroupRadioListLabel'
export default {
title: 'molcules/el/RadioGroupRadioListLabel',
component: RadioGroupRadioListLabel,
argTypes: {},
}
// eslint-disable-next-line no-empty-pattern
export const Basic = (_args, { argTypes }) => ({
components: { RadioGroupRadioListLabel },
props: Object.keys(argTypes),
template:
'<molcules-el-radio-group-radio-list-label v-bind="$props">Basic</molcules-el-radio-group-radio-list-label>',
})
Basic.args = {
options: [
{ key: 777, label: 'メイドちゃん1' },
{ key: 888, label: 'メイドちゃん2' },
],
value: -1,
}
export const NotAll = (_args, { argTypes }) => ({
components: { RadioGroupRadioListLabel },
props: Object.keys(argTypes),
template:
'<molcules-el-radio-group-radio-list-label v-bind="$props">NotAll</molcules-el-radio-group-radio-list-label>',
})
NotAll.args = {
options: [
{ key: 777, label: 'メイドちゃん1' },
{ key: 888, label: 'メイドちゃん2' },
],
value: 8,
isVisibleAll: false,
}
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
37
// components/molcules/el/RadioGroupRadioListLabel.stories.mdx
import { Meta, Story, Props, Canvas } from '@storybook/addon-docs/blocks'
import { action } from '@storybook/addon-actions'
import RadioGroupRadioListLabel from './RadioGroupRadioListLabel'
<Meta
title="molcules/el/RadioGroupRadioListLabel/Mdx"
component={RadioGroupRadioListLabel}
/>
# 概要
ElementUI のラジオボタン+テキストラベル、el-col を組み合わせた、Molcule コンポーネントです。
## サンプル
### 表示
export const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { RadioGroupRadioListLabel },
template:
'<molcules-el-radio-group-radio-list-label
v-bind="$props">NotAll</molcules-el-radio-group-radio-list-label>',
viewMode: 'docs',
})
<Canvas>
<Story
name="Basic"
args={{
options: [
{ key: 777, label: 'メイドちゃん1' },
{ key: 888, label: 'メイドちゃん2' },
],
value: -1,
}}
>
{Template.bind({})}
</Story>
</Canvas>
### コード
```html
<molcules-el-radio-group-radio-list-label
v-model="プロパティの名前"
:span="8"
:options="[
{ key: 7, label: 'メイドちゃん1' },
{ key: 8, label: 'メイドちゃん2' },
]"
>
NotAll
</molcules-el-radio-group-radio-list-label>
```
## 補足
- span は ElementUI の el-col タグの span 値になります。
- デフォルトのスロットは、ラベル名になります。
## 属性
<Props of={RadioGroupRadioListLabel} />
Copyright (C) 2021 Toranoana Inc. All Rights Reserved.
38