Example Usage
Using ivue is very simple, but you need to understand a few principles. See the examples below expanding on each principle.
Basic Usage
Classic Counter example built with ivue
<script setup lang="ts">
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
}
const counter = ivue(Counter);
</script>
<template>
<button @click="() => counter.increment()">
Count: {{ counter.count }}
</button>
</template>For this example we initialize the component like this:
<template>
<CounterBasic />
</template>NOTICE: JavaScript Class Caveat
See () => counter.increment(). You cannot simply use counter.increment to refer to the method when it was defined as an object of a class, because that method would not know that it has to be bound to counter. You have to either use () => counter.increment() or counter.increment.bind(counter), but the latter example is too verbose, so the preferred more readable way is to use an arrow function.
Using Props
See the highlighted sections related to props.
<script setup lang="ts">
import { ivue, iref } from 'ivue';
interface CounterProps {
initialCount: number;
}
const props = defineProps<CounterProps>();
class Counter {
constructor(public props: CounterProps) {
this.count = this.props.initialCount;
}
count = iref(0);
increment() {
this.count++;
}
}
/**
* NOTICE that: ivue(ClassName, ...args) uses TypeScript to infer
* the correct argument types that you need to pass to the constructor.
*/
const counter = ivue(Counter, props);
</script>
<template>
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Count: {{ counter.count }}
</template>For this example we initialize the component like this:
<template>
<CounterWithProps :initial-count="5" />
</template>Count: 5
Using Emits
See the highlighted sections related to using emits.
<script setup lang="ts">
import { ivue, iref } from 'ivue';
interface CounterProps {
initialCount: number;
}
interface CounterEmits {
(e: 'increment', count: number): void;
}
const props = defineProps<CounterProps>();
const emit = defineEmits<CounterEmits>();
class Counter {
constructor(public props: CounterProps, public emit: CounterEmits) {
this.count = this.props.initialCount;
}
count = iref(0);
increment() {
this.count++;
this.emit('increment', this.count);
}
}
/**
* NOTICE that: ivue(ClassName, ...args) uses TypeScript to infer
* the correct argument types that you need to pass to the constructor.
*/
const counter = ivue(Counter, props, emit);
</script>
<template>
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Count: {{ counter.count }}
</template>For this example we initialize the component like this:
<script setup lang="ts">
function onIncrement(value: number) {
alert('Count is now: ' + value);
}
</script>
<template>
<CounterWithPropsAndEmits :initial-count="5" @increment="onIncrement" />
</template>Count: 5
Using Refs
Refs defined externally in the component outside of the class.
import { ref } from 'vue';
import { ivue, iref, iuse } from 'ivue';
class Counter {
constructor(public span?: HTMLElement) {}
count = iref(0);
increment() {
this.count++;
(this.span as HTMLElement).innerHTML = String(this.count + 1);
}
}
const span = ref<HTMLElement>();
const counter = ivue(Counter, iuse(span));<script setup lang="ts">
// @filename: node_modules/@types/ivue/index.d.ts
// @include: ivue
// ---cut---
import { ref } from 'vue';
import { ivue, iref, iuse } from 'ivue';
class Counter {
constructor(public span?: HTMLElement) {}
count = iref(0);
increment() {
this.count++;
(this.span as HTMLElement).innerHTML = String(this.count + 1);
}
}
const span = ref<HTMLElement>();
const counter = ivue(Counter, iuse(span));
defineExpose<Counter>(counter);
</script>
<template>
<button @click="() => counter.increment()">
Count: {{ counter.count }} + 1 = <span ref="span"></span>
</button>
</template>For this example we initialize the component like this:
<template>
<CounterExternalRefs />
</template>Refs defined internally inside of the class.
<script setup lang="ts">
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
span = iref<HTMLElement>();
increment() {
this.count++;
(this.span as HTMLElement).innerHTML = String(this.count + 1);
}
}
const counter = ivue(Counter);
const { span } = counter.toRefs(['span']);
/**
* Or you can use `const { span } = counter.toRefs();`
* but `const { span } = counter.toRefs(['span']);` is more performant
* because we are only looping through 1 property instead of all of them.
*/
</script>
<template>
Count: {{ counter.count }} <span ref="span"></span><br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a>
</template>For this example we initialize the component like this:
<template>
<CounterInternalRefs />
</template>Increment
Using Define Expose
Use class as interface for defineExpose()
See the highlighted sections related to defineExpose.
<script setup lang="ts">
import { ivue } from 'ivue';
import Counter from './CounterDefineExposeClass'
const counter = ivue(Counter);
defineExpose<Counter>(counter);
</script>
<template>
Count: {{ counter.count }}
</template>import { iref } from 'ivue';
export default class Counter {
count = iref(0);
increment() {
this.count++;
}
decrement() {
this.count--;
}
}For this example we initialize the component like this:
<script setup lang="ts">
const defineExposeRef = ref<CounterDefineExposeClass | null>(null);
function increment() {
defineExposeRef.value.increment();
}
function decrement() {
defineExposeRef.value.decrement();
}
</script>
<template>
<button @click="increment">
Increment via Component Ref from Parent Component</button
><br />
<button @click="decrement">
Decrement via Component Ref from Parent Component
</button>
<CounterDefineExpose />
</template>Pick allowed interface properties for defineExpose()
<script setup lang="ts">
import { ivue, iref } from 'ivue';
/**
* See that only 'count', 'increment' properties are picked
* for the define expose interface.
*/
export type CounterExposed = Pick<Counter, 'count' | 'increment'>;
class Counter {
count = iref(0);
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
const counter = ivue(Counter);
defineExpose<CounterExposed>(counter);
</script>
<template>Count: {{ counter.count }}</template>Using Composables
Assign Vue 3 Composable to a class property
See the highlighted sections related to composable usage.
<script setup lang="ts">
import { useMouse, usePointer } from '@vueuse/core';
import { ivue, iref, iuse } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
mouse = iuse(useMouse);
pointer = iuse(usePointer);
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
<br />
Mouse: <br />
X: {{ counter.mouse.x }} <br />
Y: {{ counter.mouse.y }} <br />
<br />
Pointer: <br />
X: {{ counter.pointer.x }} <br />
Y: {{ counter.pointer.y }} <br />
</template>For this example we initialize the component like this:
<template>
<CounterComposables />
</template>Increment
Mouse:
X: 0
Y: 0
Pointer:
X: 0
Y: 0
Destructure Vue 3 Composable into the class
See the highlighted sections related to destructuring composable usage.
<script setup lang="ts">
// @filename: node_modules/@types/ivue/index.d.ts
// @include: ivue
// @include: useCustomMouse
// ---cut---
import { ivue, iuse, iref, type Use } from 'ivue';
import { useCustomMouse } from '@/components/usage/functions/useCustomMouse';
/**
* Use the ivue Utility Type: Use<typeof YourComposableFunctionName>
* to get he resulting unwrapped composable properties and functions.
*/
type UseCustomMouse = Use<typeof useCustomMouse>;
class Counter {
count = iref(0);
increment() {
this.count++;
}
/**
* 'x', 'y', 'sum', 'total' are Refs that will be unwrapped to their bare raw types and destructured into the class.
* Even though unwrapped (de-Refed), they will maintain their behavior as Refs and thus will maintain reactivity
* and at the same time get destructured into this class root level scope because
* Vue 3's `reactive()` Proxy will be able to resolve those Refs internally.
*/
x: UseCustomMouse['x']; // Unwrapped Ref<number> becomes -> number
y: UseCustomMouse['y']; // Unwrapped Ref<number> becomes -> number
sum: UseCustomMouse['sum']; // 'sum' method that will be destructured into this class on construct()
total: UseCustomMouse['total']; // 'total' computed Ref that will also be destructured into this class on construct()
constructor() {
({
x: this.x,
y: this.y,
sum: this.sum,
total: this.total,
} = iuse(useCustomMouse, 5));
}
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Mouse X: {{ counter.x }}, Y: {{ counter.y }}
<br />
Total (computed): {{ counter.total }}
<br />
<button class="button" @click="() => counter.sum()">
Click This Big Sum Button To Total X + Y
</button>
</template>import { computed, ref, type Ref } from 'vue';
import { useMouse } from '@vueuse/core';
type UseMouse = { x: Ref<number>; y: Ref<number> };
export function useCustomMouse(requiredProp: number) {
const { x, y }: UseMouse = useMouse();
const _sum = ref(0);
function sum() {
_sum.value = x.value + y.value + requiredProp;
}
const total = computed(() => {
return _sum.value;
});
return {
x,
y,
_sum,
sum,
total,
};
}For this example we initialize the component like this:
<template>
<CounterComposablesDestructuring />
</template>Increment
Mouse X: 0, Y: 0
Total (computed): 0
Assign ivue Composables to a class property
Using ivue based composables assigned to a class property:
<script setup lang="ts">
import { CustomMouse } from './classes/CustomMouse';
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
mouse = ivue(CustomMouse, 5);
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Mouse X: {{ counter.mouse.x }}, Y: {{ counter.mouse.y }}
<br />
Total (computed): {{ counter.mouse.total }}
<br />
<button class="button" @click="() => counter.mouse.sum()">
Click This Big Sum Button To Total X + Y
</button>
</template>import { useMouse } from '@vueuse/core';
import { iref, iuse, type Use } from 'ivue';
type UseMouse = Use<typeof useMouse>;
export class CustomMouse {
x: UseMouse['x'];
y: UseMouse['y'];
_sum = iref(0);
constructor(public requiredProp: number) {
({ x: this.x, y: this.y } = iuse(useMouse));
}
sum() {
this._sum = this.x + this.y + this.requiredProp;
}
get total() {
return this._sum;
}
}For this example we initialize the component like this:
<template>
<CounterComposablesIvue />
</template>Increment
Mouse X: 0, Y: 0
Total (computed): 0
Destructure ivue Composables Into Class Scope
See the sections related to using ivue based composables initialized through class destructuring assignment.
<script setup lang="ts">
import { CustomMouse } from './classes/CustomMouse';
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
/**
* 'x', 'y', 'total' are Refs that will be unwrapped to their bare raw types and destructured into the class.
* Even though unwrapped (de-Refed), they will maintain their behavior as Refs and thus will maintain reactivity
* and at the same time get destructured into this class root level scope because
* Vue 3's `reactive()` Proxy will be able to resolve those Refs internally.
*/
x: CustomMouse['x']; // Unwrapped Ref<number> becomes -> number
y: CustomMouse['y']; // Unwrapped Ref<number> becomes -> number
total: CustomMouse['total']; // Unwrapped ComputedRef<number> becomes -> number
sum: CustomMouse['sum']; // Function remains a function
constructor() {
({
x: this.x,
y: this.y,
sum: this.sum,
total: this.total,
} = ivue(CustomMouse, 5).toRefs(true));
}
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Mouse X: {{ counter.x }}, Y: {{ counter.y }}
<br />
Total (computed): {{ counter.total }}
<br />
<button class="button" @click="() => counter.sum()">
Click This Big Sum Button To Total X + Y
</button>
</template>import { useMouse } from '@vueuse/core';
import { iref, iuse, type Use } from 'ivue';
type UseMouse = Use<typeof useMouse>;
export class CustomMouse {
x: UseMouse['x'];
y: UseMouse['y'];
_sum = iref(0);
constructor(public requiredProp: number) {
({ x: this.x, y: this.y } = iuse(useMouse));
}
sum() {
this._sum = this.x + this.y + this.requiredProp;
}
get total() {
return this._sum;
}
}For this example we initialize the component like this:
<template>
<CounterComposablesIvueDestructuring />
</template>Increment
Mouse X: 0, Y: 0
Total (computed): 0
Using Inside Composables
See the highlighted sections related to using ivue inside a composable.
<script setup lang="ts">
// @filename: node_modules/@types/ivue/index.d.ts
// @include: ivue
// ---cut---
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
}
function useCounter() {
/** Convert ivue Counter to a composable with .toRefs() */
return ivue(Counter).toRefs();
}
const { count, increment } = useCounter();
</script>
<template>
<button @click="() => increment()">
Count: {{ count }}
</button>
</template>For this example we initialize the component like this:
<template>
<CounterInsideComposables />
</template>Using Computeds
Getters are computeds in ivue unless disabled.
See the highlighted sections related to getters.
<script setup lang="ts">
import { ivue, iref } from 'ivue';
class Counter {
count = iref(0);
increment() {
this.count++;
}
get doubleCount() {
return this.count * 2;
}
get quadCount() {
return this.doubleCount * 2;
}
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Double Count: {{ counter.doubleCount }} <br />
Quad Count: {{ counter.quadCount }} <br />
</template>For this example we initialize the component like this:
<template>
<CounterComputeds />
</template>Increment
Double Count: 0
Quad Count: 0
Disable computed behavior for certain getters in ivue.
You can disable computed getters via static ivue = { getter: false }, see below:
<script setup lang="ts">
import { ivue, iref } from 'ivue';
class Counter {
/**
* Use static ivue property to disable computed
* behavior for certain getters.
*/
static ivue = {
doubleCount: false, // doubleCount is a regular getter now
};
count = iref(0);
increment() {
this.count++;
}
get doubleCount() {
return this.count * 2;
}
get quadCount() {
return this.doubleCount * 2;
}
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} <br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Double Count: {{ counter.doubleCount }} <br />
Quad Count: {{ counter.quadCount }} <br />
</template>The result of this example is identical to the above.
Using Watch
To use watch, watchEffect, and other reactive functions, declare .init() method in the class. See the highlighted sections related to using init() below:
<script setup lang="ts">
import { watch } from 'vue';
import { ivue, iref } from 'ivue';
class Counter {
init() {
/** Note that we use this.count as a reactive prop in the watch() function */
watch(
() => this.count,
(newCount) => {
if (newCount === 5) {
alert('You already clicked 5 times!');
}
}
);
}
count = iref(0);
increment() {
this.count++;
}
}
const counter = ivue(Counter);
</script>
<template>
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a><br />
Count: {{ counter.count }} (Click 5 times to get an alert)
</template>For this example we initialize the component like this:
<template>
<CounterWatch />
</template>Count: 0 (Click 5 times to get an alert)
Using Lifecycle Hooks
To use onMounted, onBeforeMount and other lifecycle hooks, declare .init() method in the class. See the highlighted sections related to using init() below:
NOTICE:
init() method can be declared as async if needed.
<script setup lang="ts">
import { onMounted } from 'vue';
import { ivue, iref } from 'ivue';
class Counter {
init() {
onMounted(() => {
this.count = 100;
});
}
count = iref(0);
increment() {
this.count++;
}
}
const counter = ivue(Counter);
</script>
<template>
Count: {{ counter.count }} (onMounted we set it to 100)<br />
<a href="javascript:void(0)" @click="() => counter.increment()">
Increment
</a>
</template>For this example we initialize the component like this:
<template>
<CounterLifecycleHooks />
</template>Increment
