こちらはVue.js #4 Advent Calendar 2017 - Qiita 8日目の記事です。
株式会社SCOUTERの鍬(id:yusuke_kuwa) です。
今回は、Vue.jsらしい美しいコードだなと思ったドロップダウンのコンポーネント作成に焦点を当てて、作っていく工程を振り返ってみました。
検証実施環境
Vue.js ^2.5.0
Element ^2.0.0
lodash-es ^4.17.4
yarn ^1.0.0
コンポーネントの要件
今回は、以下の要件で使用されるコンポーネントとして作成しています。
テーブルや検索結果の件数指定・並び替えなどに使用できる
今なにを選択しているのか分かる
選択した直後にリクエストが送信される
完成イメージはgithubのDropDownです。
変数とpropsの設計
まずは、コンポーネントとして受け取る値を決めていきます。 上の要件を満たすように実装していきます。
props: { label: { type: String, required: true, }, listItems: { type: Object, required: true, }, activeItemKey: { type: [String, Number], required: false, }, action: { type: Function, required: true, }, }, data() { return { isActive: false, }; },
label
はgithubの図でいう "sort"の部分listItems
は選択肢をkey:valueで受け取るObjectactiveItemKey
は選択中のlistItem
のkeyの値action
はドロップダウンの中身のlistItem
を選択した時の実行関数
となっています。
今回作成するドロップダウンはあくまでもパーツなので、実行関数は親コンポーネントに持たせます。
親コンポーネント側のactionの役割は、
リクエストを送る
activeItemKey
の値を更新する
などを想定しています。
実装
親コンポーネントから渡ってくる値が決まったので、ガシガシと作っていきます。
templateタグ周り
<template> <div> <div class="all-wrapper"> <div class="dropdown-wrapper" @click="isActive = !isActive"> <div class="dropdown-text"> {{label}} </div> <i class="el-icon-caret-bottom"></i> </div> <transition> <div class="list-items" v-if="isActive"> <template v-if="existsListItems"> <template v-for="(value, key) in listItems"> <div class="list-item" :class="[key == activeItemKey ? 'active' : '' ]" @click="handleClickItem(key)" > {{value.name}} </div> </template> </template> <template v-else> <div class="list-item"> 選択肢がありません </div> </template> </div> </transition> </div> <div class="dropdown-bg" @click="isActive = false" v-if="isActive"></div> </div> </template>
propsから受け取る値の表示・制御はtemplateタグの中で済んでしまいます。
選択中の要素かどうかのactive判定も、propsで受け取ったactiveItemKey
を使ってテンプレート内で処理してしまいます。
最終的にscriptタグに残る記述は、
computed: { existsListItems() { return !isEmpty(this.listItems); }, }, methods: { handleClickItem(key) { if (key == this.activeItemKey) { return; } this.isActive = false; this.action(key); }, },
たったこれだけになりました。良いですね!
疑似背景
また、背景押下時にドロップダウンを閉じる挙動を
<div class="dropdown-bg" @click="isActive = false" v-if="isActive"></div>
が扱っており、CSSで疑似背景を全画面に展開しています。
.dropdown-bg { width: 100vw; height: 100vh; position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 2; }
スタイリング
ではいよいよ、list-item
にスタイルを当てていきます。
.all-wrapper { position: relative; .dropdown-wrapper { color: #666666; display: flex; align-items: center; &:hover { cursor: pointer; } .dropdown-text { font-size: 14px; } i { font-size: 10px; margin-left: 6px; } } .list-items { width: 260px; max-height: 300px; background-color: #fff; border-radius: 2px; border: 1px solid #B9BFC9; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04); position: absolute; right: 0; overflow-y: scroll; z-index: 3; padding: 0.5rem 0; .list-item { color: #333; font-size: 14px; line-height: 16px; padding: 0.75rem 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative; &:not(.active):hover { background-color: #F3F4F6; cursor: pointer; } &.active { color: #fff; background-color: #182A4B; } } } }
背景の上にlits-item
が乗るので、こちらはz-index:3
となります。
仕上げのtransition
最後にtransition
に、ヌルっと表れるcssを当てて仕上がりです。
Vue.jsのtransactionコンポーネントを使用しているので、クラスの制御はVue.jsに任せて、CSSを書くだけで実装できます。
参考: Enter/Leave とトランジション一覧 — Vue.js
.v-enter-active, .v-leave-active { transition: all 0.3s } .v-enter { transform: translateY(-10px); } .v-enter, .v-leave-to { opacity: 0 }
完成品
まとめ
今回はVue.jsの習熟度的な観点で難しいことは特にしていませんが、props,template,cssの役割をうまく分散させる設計が肝でしたね。
jQuery等はもちろんですが、Reactでもここまで綺麗に書けないのではないかなと思います。笑
よきVue.jsライフを!