Vue.js始めるおれおれアドベントカレンダー2016 – 16日目分(24日公開)

アニメーション付きのナビバーを作ってみました、簡単でした。やったね。
- デモ: https://ginpei.github.io/vue-ginpei-advent-calendar-2016/16-mounted/
- コード: https://github.com/ginpei/vue-ginpei-advent-calendar-2016/tree/master/16-mounted
基本的な作り方
location.hashを監視して情報更新- hashに該当する内容を表示
- バー位置をhashの候補の順序から算出
簡単に作れたは良いんだけど、最後のやつどうしようかなと。
バー位置をhashの候補の順序から算出
最初に書いたコードはこう。
<nav> <a href="#">Home</a> <a href="#about">About</a> <a href="#contact">Contact</a> <span :style="underlineStyle"></span> </nav>
const store = require('./store.js')
const hashes = ['', '#about', '#contact']
module.exports = {
data () {
return store.state
},
computed: {
underlineStyle () {
const itemWidth = 100
const left = itemWidth * hashes.indexOf(this.hash)
return {
transform: 'translateX(' + left + 'px)'
}
}
}
}
これで全然動くんだけど、疑問点が二つ。
- 候補値
'','#about','#contact'をHTML側と共有しているの、どうにかならんかな - 項目の幅
100をCSS側と共有しているの、どうにかならんかな
jQueryであれば実際の要素を見てあれこれするんだけど、Vueはそうはしないじゃないすか。普通。
$refs を使う?
とか言いつつ要素を見てあれこれするやつ、使ったらできるにはできた。
<nav ref="list"> <a href="#">Home</a> <a href="#about">About</a> <a href="#contact">Contact</a> <span :style="underlineStyle"></span> </nav>
computed: {
/**
* 項目の幅の実測値を返す。
*/
itemWidth () {
const elList = this.$refs.list
const elItem = elList.firstElementChild
return elItem.clientWidth
},
/**
* 項目の `href` からhash候補値を得る。
*/
hashes () {
const elList = this.$refs.list
const elItems = elList.children
const hashes = Array.from(elItems).map(elItem => {
let hash = elItem.getAttribute('href')
if (hash === '#') {
hash = ''
}
return hash
})
return hashes
},
underlineStyle () {
let left
// 最初のDOM構築の際には当然underlineStyleは呼ばれるが、
// 最初だからDOMがまだないので、 `$refs` が使えない。
if (this.$refs.list) {
left = this.itemWidth * this.hashes.indexOf(this.hash)
} else {
// `hash` 変更時にキャッシュ値を更新するよう、
// ここで呼んで記憶してもらう
left = this.hash.length * 0
}
return {
transform: 'translateX(' + left + 'px)'
}
}
},
うわあ、すごく危険な香りがする。
実際公式ガイドにもやめてねって書いてあるし。
$refsはコンポーネントが描画された後にのみ追加されます。そしてそれはリアクティブではありません。直接子コンポーネントを操作するための最終手段としての意味しかありません。テンプレートまたは算出プロパティの中での$refsの使用は避けるべきです。
うん。
テンプレートまたは算出プロパティの中での
$refsの使用は避けるべきです。
そう思います。
マウントのタイミングで確認したい……
……マウント……ライフサイクル……ん?
module.exports = {
data () {
return {
itemWidth: 999,
hashes: [],
state: store.state
}
},
mounted () {
const elList = this.$refs.list
const elItems = elList.children
this.itemWidth = elItems[0].clientWidth
this.hashes = Array.from(elItems).map(elItem => {
let hash = elItem.getAttribute('href')
if (hash === '#') {
hash = ''
}
return hash
})
},
computed: {
underlineStyle () {
const left = this.itemWidth * this.hashes.indexOf(this.state.hash)
return {
transform: 'translateX(' + left + 'px)'
}
}
}
}
なるほど、こういう感じか。これなら良さそう。