Twitter以上ブログ以下

ただの読み物

Vue + slick (jquery plugin) でハマった話

この記事でわかること

背景

気持ちとしてはこういうことをしたかった。

vueシンタックスハイライトが効かなかったので別々で……

<template>
  <div class='cards is-slick'>
    <div class='card' v-for="element in elements">
      <element></element>
    </div>
  </div>
</template>
export default Vue.extend({
  data() {
    return {
      elements: []
    }
  },
  methods: {
    async getElements(){ HTTP.get('/api/elements').map(x => Element.new(x)) }
  },
  // mounted か created かはともかく
  async created() {
    this.elements = await getElements()
  }
})

雰囲気はわかってくれると思う。雰囲気はね。 で、問題は <div class='cards is-slick'>slick でカルーセルにしたい。

なので、とりあえずこうやって見る

// ...
mounted() {
  $('.is-slick').slick()
}

これは動かない。何故かと言うと、 slick はそのイベントが発火した時点のdivしか補足できず、新たに読み込まれた div はカルーセルの対象外になってしまうためだ。 つまり

  <div class='cards is-slick'>
    <div class='card' v-for="element in elements">
    </div>
  </div>

この状態で slick() を発火しても意味がないということだ。 これをどうにかしたい。

ずさんな解決方法

つまり、全てのElementが用意された時点で slick() を発火してやれば良い。

export default Vue.extend({
  data() {
    return {
      elements: [],
      isSlicked: false
    }
  },
  methods: {
    async getElements(){ 
      await HTTP.get('/api/elements').map(x => Element.new(x)) 
    }
  },
  async created() {
    this.elements = await getElements()
  },
  watch: {
    elements(vals, oldVals) {
      if (!this.isSlicked) return;

      $('.is-slick').slick('unslick')
      this.isSlicked = false
    }
  }
  updated() {
    if(!this.isSliced) {
      $('.is-slick').slick()
    }
/**  2018/02/16 11時05分 更新
    this.$nextTick(() => {
      try {
        // 既に slick() してあるElementに対してもう一度発火すると落ちる。
        // 逆に、まだ slick() していないElementに対して `unslick` を発火しても落ちる。
        // ほんに〜〜〜〜〜?????????
        $('.is-slick').slick('unslick')
      } catch(_){ 
      } finally {
        $('.is-slick').slick()
      }
*/
    })
  }
})

updated$nextTick() を使うと全てのRenderが完了してから呼ばれる。 これで解決したけど、ずさんすぎる気がする。 たぶんComponentのライフサイクルの設計が間違っているんだと思う。

誰か良さげな回避策を教えて……ください……

  • そもそも slick を利用しないという選択
  • ルーセルくらい自前で作れるだろ感
  • etc...