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));
defineExpose<Counter>(counter);
</script>
<template>
<script setup lang="ts">
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">
// @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 } from 'vue';
import { useMouse } from '@vueuse/core';
export function useCustomMouse(requiredProp: number) {
const { x, y } = useMouse();
const _sum = ref(0);
function sum() {
_sum.value = x.value + y.value + requiredProp;
}
const total = computed(() => {
// Returning without .value, not entirely correctly, but will still work in Vue template.
// This is to test the impressive IVue unwrapping capabilities of deeply nested and confusing Refs
return _sum;
});
return {
x,
y,
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">
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