Vue コンポーネントにおけるロジック再利用のサンプル。Mixins から Composition API まで。

^3.0.0-rc.1 で動作確認しています。Composition API パターン以外は 2 系でも基本的には動くと思います。

サンプル

マウスカーソルのポジションを表示するコンポーネントをお題にしています。よくあるやつです。

まずは普通に

<template>{{ x }} {{ y }}</template>
<script>
export default {
  data() {
    return {
      x: 0,
      y: 0,
    };
  },
  mounted() {
    window.addEventListener("mousemove", this.update);
  },
  unmounted() {
    window.removeEventListener("mousemove", this.update);
  },
  methods: {
    update(e) {
      this.x = e.pageX;
      this.y = e.pageY;
    },
  },
};
</script>

Mixins

<template>{{ x }} {{ y }}</template>
<script>
import mixinMouse from './mixin-mouse';
export default {
  mixins: [mixinMouse]
};
</script>

mixin-mouse.js

export default {
  data() {
    return {
      x: 0,
      y: 0,
    };
  },
  mounted() {
    window.addEventListener("mousemove", this.update);
  },
  unmounted() {
    window.removeEventListener("mousemove", this.update);
  },
  methods: {
    update(e) {
      this.x = e.pageX;
      this.y = e.pageY;
    },
  },
};

Higher-Order Components (HOCs)

Position.vue

<template>{{ x }} {{ y }}</template>
<script>
export default {
  props: ["x", "y"],
};
</script>

with-mouse.js

import { h } from "vue";
export function hoc(inner) {
  return {
    data() {
      return {
        x: 0,
        y: 0,
      };
    },
    mounted() {
      window.addEventListener("mousemove", this.update);
    },
    unmounted() {
      window.removeEventListener("mousemove", this.update);
    },
    methods: {
      update(e) {
        this.x = e.pageX;
        this.y = e.pageY;
      },
    },
    render() {
      return h(inner, {
        x: this.x,
        y: this.y,
      });
    },
  };
}
import Position from "./components/Position.vue";
import withMouse from "./components/with-mouse";

const MousePosition = withMouse(Position);

Scoped Slots

React でいうところの Render Props

Mouse.vue

<template>
  <slot :mx="x" :my="y"></slot>
</template>
<script>
export default {
  data() {
    return {
      x: 0,
      y: 0,
    };
  },
  mounted() {
    window.addEventListener("mousemove", this.update);
  },
  unmounted() {
    window.removeEventListener("mousemove", this.update);
  },
  methods: {
    update(e) {
      this.x = e.pageX;
      this.y = e.pageY;
    },
  },
};
</script>
<Mouse>
  <template v-slot="{mx, my}">{{ mx }} {{ my }}</template>
</Mouse>  

Composition API

<template>{{ x }} {{ y }}</template>
<script>
import useMouse from "./use-mouse";
export default {
  setup() {
    const { x, y } = useMouse();
    return { x, y };
  },
};
</script>

use-mouse.js

import { ref, onMounted, onUnmounted } from 'vue'

export default function useMouse() {
  const x = ref(0);
  const y = ref(0);
  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }
  onMounted(() => {
    window.addEventListener("mousemove", update);
  });
  onUnmounted(() => {
    window.removeEventListener("mousemove", update);
  });
  return { x, y };
}