56
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.jsのmixinをslotで代用する

Last updated at Posted at 2018-05-23

Vueにはmixinという機能がある。

同一の機能を複数のコンポーネントに適応させるというものだ。

共通の機能を提供する便利なものだが、あまり楽観的に使えるものではない。

例えばReactでも過去には存在していたが、既に廃止されている。
なぜ廃止されたかは理由を読めばmixinのどのような点が危険な点かが見えてくるだろう。

ざっくりと要約するとこのへんが上げられている

  • 暗黙の依存関係を導入してしまう
  • 名前の衝突を起こす
    • 複数のmixinがあり、それらがプロパティを上書きするために名前が衝突してしまう
  • 拡張のたびに雪崩的に複雑さを引き起こす

そこでVue.jsでもmixinを使わない方法を今回は記述したい

どうするか?

Reactでは高次コンポーネントを導入することで解決している。
Vue.jsにおいては<slot>を使えばこれは多くの場合解決出来そうだ。

(render propsによる方法もあるが、こちらは下記の記事におまかせしたい。
Vue.jsにおけるRender PropとScoped Slotsについて

今回は例としてマウスイベントをトラッキングする処理を考える。

まずはmixinを使った実装

マウスイベントをとるmixinはこんな感じ。

// mouseMixin.js
export default {
  data() {
    return {
      x: 0,
      y: 0
    };
  },
  methods: {
    mousemove(e) {
      this.x = e.clientX;
      this.y = e.clientY;
    }
  }
}

利用側はこんな具合になる。

<!-- Item -->
<template>
  <div @mousemove="mousemove">
    <div>Item with mixin</div>
    <div>x: {{ x }}</div>
    <div>y: {{ y }}</div>
  </div>
</template>

<script>
import mouseMixin from "./mouseMixin"

export default {
  mixins: [ mouseMixin ],
};
</script>

呼び出しはあとは下記のような感じになる。

<!-- Parent.vue -->
<template>
  <div>
    <Item />
  </div>
</template>

<script>
export default {
  components: {
    Item,
  }
};
</script>

先のmixinの問題点として上げたとおり、x,y,mousemoveが暗黙的になっており、定義を探すのにmouseMixinをたどることになる。
mixinが一つぐらいなら問題になることは少ないかもしれないが、これが増えてくると厳しくなってくるだろう。

slotにする

まずmixinを利用する側。
xyをもらうだけで至ってシンプル

<!-- Item.vue -->
<template>
  <div>
    <div>Item with scope</div>
    <div>x: {{ x }}</div>
    <div>y: {{ y }}</div>
  </div>
</template>

<script>
export default {
  props: {
    x: Number,
    y: Number
  }
};
</script>

次にMouseEventの処理をするMouseEvent.vueを分離したもの。
mousemoveを取得してslotに対してxyを渡す。

<!-- MouseEvent.vue -->
<template>
  <div @mousemove="mousemove">
    <slot :x="x" :y="y"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      x: 0,
      y: 0
    };
  },
  methods: {
    mousemove(e) {
      this.x = e.clientX;
      this.y = e.clientY;
    }
  }
};
</script>

最後に組み合わせるところ。<MouseEvent>の子としてslot-scopeを定義しItemに渡す

<!-- Parent.vue -->
<template>
  <div>
    <MouseEvent>
      <div slot-scope="{x, y}">
        <Item :x="x" :y="y" />
      </div>
    </MouseEvent>
  </div>
</template>

<script>
export default {
  components: {
    MouseEvent,
    Item,
  },
};
</script>

まとめ

  • Vueだとscopeを使ってしまう場合、記述量が増えて冗長になってしまうのは否めない。
    • 願わくばもう少し簡易に書けると嬉しいが・・・・
    • そしてscope-slotの使い方は覚えづらいので慣れが必要な感じ
  • とはいえ、それぞれ暗黙的な呼び出しは無くなったり、複数組み合わせても名前の衝突が起きないことは利点だろう。
  • mixinを避けたい場合はそこそこあるので、使えるパターンだろう。
56
47
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
56
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?