Skip to content

Counter

The smallest complete ivue class: one piece of mutable state, one derived value, two actions. count is a ref-getter, double is a plain getter — no computed() — and the methods write through .value.

What to notice

  • double allocates nothing. It is a plain getter, re-derived on render — zero bytes per instance, fully reactive through leaf tracking.
  • Methods are engine-bound. counter.increment is safe to pass as a handler directly; its identity is stable.

The source

The demo above runs these exact files from the playground:

ts
// Counter.ts — the first class: one ref, one plain getter, two methods.
import { ref } from 'vue';
import { Reactive } from '../../ivue';

class $Counter {
  get count() {
    return ref(0);
  }

  // Derived value: a plain getter. No computed() needed for simple math.
  get double() {
    return this.count.value * 2;
  }

  increment() {
    this.count.value++;
  }

  reset() {
    this.count.value = 0;
  }
}

export namespace Counter {
  export const $Class = $Counter; // 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 { Counter } from './Counter';

const counter = new Counter.Class();

// the state destructure
const {
  // state refs
  count,
} = counter;
</script>

<template>
  <div class="pane">
    <p class="note">
      double is a plain getter, not a computed(). It re-derives whenever the
      component renders.
    </p>
    <div class="vals">
      <div>
        <div class="k">count</div>
        <div class="n">{{ count }}</div>
      </div>
      <div>
        <div class="k">double · plain getter</div>
        <div class="n grad">{{ counter.double }}</div>
      </div>
    </div>
    <div class="row">
      <button class="btn primary" type="button" @click="counter.increment()">
        +1
      </button>
      <button class="btn" type="button" @click="counter.reset()">Reset</button>
    </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.