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

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


まえがき

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


コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)