LoginSignup
12
9

More than 5 years have passed since last update.

小さなVueコンポーネントを作るときに気をつけるべきパターン with Storybook

Posted at

まえがき

VueでAtomicデザインの考え方を取り入れてAtomに相当する小さなコンポーネントを作っていくときに気をつけておきたいパターンです
Atomに相当する部分のコンポーネントは、コード量や複雑度は高くないですが最も多くの依存を受けるため
一度コンポーネントのインターフェイスが固まってしまうと、後から変更するのが難しい傾向にあります。
常に利用するときの意識を持ちながらコンポーネントを作るのが大事です

※ おまけでStorybookにカタログを作っていく際に気をつけることも書いています。

Vueコンポーネント編

パターン1 : emitされるeventの不足

問題

コンポーネントを利用する際にあってほしいイベントが受け取れない

  • Buttonなのにclickイベントが発行されない
<template>
  <div>
    <!--このボタンのclickイベントは外からキャッチできない-->  
    <input type="button" value="button" /> 
  </div>
</template>
  • Inputなのにchangeイベントしかなくinputイベントでリアルタイムな検証ができない

解決策

  • 標準のDOMと違和感がないようなイベントをemitする

パターン2:過剰なeventのemit

問題

mouseenter/mouseleaveなど大抵のDOMで利用できるイベントを、細かく自前でemitしなおす実装をしてしまう
デバッグツールに大量のイベントが表示されてしまいノイズになる

こういうコードを書いて

  <div @mouseleave="$emit('mouseleave')" >
  </div>

こうなる

解決策

.nativeや$listenersを使って必要なeventだけを自分でemitする
参考:コンポーネントにネイティブイベントをバインディング

パターン3:過剰なprops依存

問題

propsが多すぎて、ユーザが使いにくいコンポーネントになる

  • 表示・非表示をコントロールするだけのpropsを作る(外部からv-ifで切り替えるのと同じ)
<template>
  <div v-if="show">
  </div>
</template>

<script>
export default {
  name: "AppComponent",
  props: {
    show:Boolean
  },
}

解決策

  • そのpropsが本当にいるのかをよく考える

上の例で言えば↓のように書けばshowはいらないはず

<app-component v-if="show" />
  • slotを使った設計に切り替えてpropsを絞る

パターン4:propsの名前が直感的でない

問題

標準のHTMLやCSSで使われる単語を別の意味のpropsとして受け取れるようになっている
利用者は普段のHTMLと意味が乖離するので困惑する(バグを生みやすい)

  • buttonのtypeが意味が違う(コンポーネントではデザインのためのpropsだが、標準のbuttonではsubmitなどのロールを担う)
<!-- コンポーネント名を見て、利用者はtypeに'submit'などの標準的なbuttonのattributeが入ることを期待しがち -->
<app-button type="submit" />
AppButton.vue
<template>
  <!-- 実際は全然関係ないところにtypeが使われている -->
  <div :class="type">
    <button></button>
  </div>
</template>

<script>
export default {
  name: "AppButton",
  props: {
    type:String
  },
};
</script>

解決策

  • 常に標準のHTMLやCSSで利用される値を考慮して、概念と命名の感覚をなるべく揃える

パターン5:レイアウトのためのCSSを内包している

問題

コンポーネントの外から与えたいレイアウトのためのCSS(margin、float、position、z-indexなど)がコンポーネント内部に内包されていて、利用が難しい

  • z-indexが内包されていて外部からコントロールが難しい
Modal.vue
<template>
  <div><div class="modal"></div></div>
</template>
<style scoped>
.modal {
  z-index: 10000;
}
</style>

解決策

デザインのためのCSSとレイアウトのためのCSSを分けて考える。
レイアウトに使うCSSは「コンポーネントを利用する側」が与えられるような設計にする

App.vue
<template>
  <!-- 外からスタイルを与える -->
  <app-modal class="modal">
</template>
<style scoped>
.modal {
  z-index: 10000;
}

参考:http://apbcss.com/

パターン6:data依存

問題

親に向けてemitすればいいだけの値を、コンポーネント自身のdataに一回保存してしまう

↓のAppInputコンポーネントでは初期値しか渡せませんが、初期値以外の値を渡そうとすると、propsの上書きの警告にぶつかることが多く、単純なコードでは実現できなくなることが多いです。

AppInput.vue
<template>
  <div><input type="text" v-model="value" /></div>
</template>

<script>
export default {
  name: "AppInput",
  props: {
    initValue: String
  },
  data() {
    return {
      innerValue: this.$props.initValue
    };
  },
  computed: {
    value: {
      get() {
        return this.innerValue;
      },
      set(value) {
        this.innerValue = value;
        this.$emit("input", value);
      }
    }
  }
};
</script>

解決策

dataの項目はなるべく減らす
上のAppInputコンポーネントの例では以下のようにすればそもそもdata自体が不要です

AppInput
<template>
  <!-- propsの値をそのまま子供に渡してしまう -->
  <div><input type="text" :value="value" @input="onInput" /></div>
</template>

<script>
export default {
  name: "AppInput",
  props: {
    value: String
  },
  methods: {
    onInput(event) {
      // 親へは純粋に値の橋渡しをするだけでいい
      this.$emit("input", event.target.value);
    }
  }
};
</script>

Storybook編

storybookを作る目的はUIのカタログを作ると同時に、
利用者が実装の詳細を追わなくてもstorybookを見ればコンポーネントを利用できる状態にすることにあるので、
それが実現されているかどうかを常に考える

パターン1:propsとして渡せる値が分からない

問題

コンポーネントに渡せるプロパティとして何が用意されているのかわからない

解決策

storybook-addon-vue-infoなどを使って、コンポーネントのプロパティを出力する
Storybook.png

パターン2:propsの値のバリエーションが分からない

問題

<button type="submit">submitのように、プロパティとして渡す値の選択肢が決まっているときに、それがStorybook上からわからない

解決策

  • knobを使う
    • 値のパターンが決まっているときはselectやradioなどを使う
    • 値のON/OFFのみならcheckboxを使う

Storybook.png

パターン3:emitされるイベントが管理されていない

問題

コンポーネントから発行される、(利用を想定している)イベントに対してactionが貼られていない・送られてくるpayloadがわからない

解決策

actionを使って、想定される利用シーンを伝える
Storybook.png

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9