Skip to content

Composable in a class

Pointer hosts useMouse behind a private $-getter: the composable is created once, on the first read, and cached for the life of the instance. The public surface is two refs — x and y.

What to notice

  • The composable is an implementation detail. Swap useMouse for any other source of coordinates and no consumer changes.
  • Scope-correct teardown. The component's state destructure materializes $mouse inside setup, so its listeners are cleaned up on unmount.

The source

ts
// Pointer.ts — a class HOSTING a composable: private inside, two refs outside.
import { Reactive } from '../../ivue';
import { useMouse } from '@vueuse/core';

class $Pointer {
  // the composable is an implementation detail — created once, held forever
  private get $mouse() {
    return useMouse();
  }

  // the public surface: two refs
  get x() {
    return this.$mouse.x;
  }
  get y() {
    return this.$mouse.y;
  }

  // display derivations — touch events report fractional page coordinates
  // (23.333…); whole pixels are what a readout wants
  get pageX() {
    return Math.round(this.x.value);
  }
  get pageY() {
    return Math.round(this.y.value);
  }
}

export namespace Pointer {
  export const $Class = $Pointer; // raw — children `extends` this
  export let Class = Reactive($Class); // reactive — you `new` this
  export type Instance = typeof Class.Instance; // defineExpose type & reactive() interop
}
vue
<script setup lang="ts">
import { Pointer } from './Pointer';

// materializes $mouse inside the component's scope, so its listeners are
// cleaned up on unmount
const pointer = new Pointer.Class();
</script>

<template>
  <div class="pane">
    <p class="note">
      useMouse lives inside the class — private, created once on the first
      read. The readouts are pageX/pageY, plain getters that round the raw
      coordinates to whole pixels.
    </p>
    <div class="vals">
      <div>
        <div class="k">x · page</div>
        <div class="n">{{ pointer.pageX }}</div>
      </div>
      <div>
        <div class="k">y · page</div>
        <div class="n">{{ pointer.pageY }}</div>
      </div>
    </div>
  </div>
</template>

<style scoped src="../example-pane.css"></style>

Open in StackBlitz ⚡ — the playground boots with this example's route and file active.

Released under the MIT License.