Skip to content

Guidelines

Dos and Don'ts

ts
import type { 
ComputedRef
,
ExtractPropTypes
,
Ref
,
ToRef
} from 'vue';
import {
computed
,
markRaw
,
reactive
,
ref
,
shallowRef
,
toRef
} from 'vue';
import type {
Ref
as
DemiRef
} from 'vue-demi';
/** Cached global methods. */ const
isArray
=
Array
.
isArray
;
const
createObject
=
Object
.
create
;
const
defineProperty
=
Object
.
defineProperty
;
const
getPrototypeOf
=
Object
.
getPrototypeOf
;
const
getOwnPropertyNames
=
Object
.
getOwnPropertyNames
;
const
getOwnPropertyDescriptor
=
Object
.
getOwnPropertyDescriptor
;
const
getOwnPropertyDescriptors
=
Object
.
getOwnPropertyDescriptors
;
/** Types */ /** * IVue core reactive instance type with an extended .toRefs() method added. */ export type
IVue
<
T
extends
AnyClass
> =
InstanceType
<
T
> & {
toRefs
:
IVueToRefsFn
<
T
>;
clone
:
IVueClone
<
T
>;
}; /** * Type definition for `.clone()` method creates a deep clone of the ivue instance. */ export type
IVueClone
<
T
extends
AnyClass
> = (
...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>
) =>
IVue
<
T
>;
/** * Type definition for `.toRefs()` method converts reactive class properties to composable .value properties. * But if props = true OR unwrap = true is specified, the refs will be unwrapped refs to be able to be merged with the the root class properties without losing reactivity. */ /** Extract only non-method keys from a type */ type
NonMethodKeys
<
T
> = {
[
K
in keyof
T
]:
T
[
K
] extends
AnyFn
? never :
K
;
}[keyof
T
];
/** Create a type with only data properties and accessors */ type
DataProperties
<
T
> =
Pick
<
T
,
NonMethodKeys
<
T
>>;
/** * Simplified IVueToRefsFn that only works with data properties */ interface
IVueToRefsFn
<
T
extends
AnyClass
> {
<
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[]):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: false):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
DataProperties
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: true
):
Pick
<
DataProperties
<
InstanceType
<
T
>>,
P
>;
(
props
: true):
DataProperties
<
InstanceType
<
T
>>;
(
props
: false):
IVueRefs
<
InstanceType
<
T
>>;
():
IVueRefs
<
InstanceType
<
T
>>;
} /** * Converts a class properties to a composable .value Refs using ToRef vue type, * also knows NOT to convert functions to .value Refs but to leave them as is. */ export type
IVueRefs
<
T
> = {
[
K
in keyof
T
as
T
[
K
] extends
AnyFn
? never :
K
]:
ToRef
<
T
[
K
]>;
}; /** * Unwraps Ref Recursively, helps resolve fully the bare value types of any type T * because sometimes the Refs are returned as they are with `.value` from computeds, * thus inferring nested ComputedRef<ComputedRef<ComputedRef<Ref>>> types, which * are difficult to fully resolve to bare values without this utility. * Also support Vue 3 Demi for vue-use library support. */ type
UnwrapRefRecursively
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
['value']>
:
T
;
/** * Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types. */ type
UnwrapComposableRefs
<
T
> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
: { [
K
in keyof
T
]:
T
[
K
] extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
[
K
]>
:
T
[
K
];
}; /** * Fully unwraps to bare value types any Vue 3 composable return definition type. */ export type
Use
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
:
UnwrapComposableRefs
<
T
extends
AnyFn
?
ReturnType
<
T
> :
T
>;
/** * Extracts object defined emit types by converting them to a plain interface. */ export type
ExtractEmitTypes
<
T
extends
Record
<string, any>> =
UnionToIntersection
<
RecordToUnion
<{
[
K
in keyof
T
]: (
evt
:
K
, ...
args
:
Parameters
<
T
[
K
]>) => void;
}> >; /** * Extract properties as all assigned properties because they have defaults. */ export type
ExtractPropDefaultTypes
<
O
> = {
[
K
in keyof
O
]:
ValueOf
<
ExtractPropTypes
<
O
>,
K
>;
}; /** * Extend slots interface T with prefixed 'before--' & 'after--' slots to create fully extensible flexible slots. */ export type
ExtendSlots
<
T
> =
PrefixKeys
<
T
, 'before--'> &
T
&
PrefixKeys
<
T
, 'after--'>;
/** Helper Types. */ /** * Any JavaScript function of any type. */ export type
AnyFn
= (...
args
: any[]) => any;
/** * Any JavaScript class of any type. */ export type
AnyClass
= new (...
args
: any[]) => any;
/** * Accessors map type. */ type
Accessors
=
Map
<string, PropertyDescriptor>;
/** * Computeds hash map type. */ type
Computeds
=
Record
<string,
ComputedRef
>;
/** * Infer paramaters of a constructor function of a Class. */ export type
InferredArgs
<
T
> =
T
extends { new (...
args
: infer
P
): any }
?
P
: never[]; /** * Prefix keys of an interface T with a prefix P. */ export type
PrefixKeys
<
T
,
P
extends string | undefined = undefined> = {
[
K
in
Extract
<keyof
T
, string> as
P
extends string ? `${
P
}${
K
}` :
K
]:
T
[
K
];
}; /** * Get function arguments Parmeters<F> parameter by key K */ export type
FnParameter
<
F
extends
AnyFn
,
K
extends number> =
Parameters
<
F
>[
K
];
/** * Convert Union Type to Intersection Type. */ type
UnionToIntersection
<
U
> = (
U
extends any ? (
k
:
U
) => void : never) extends (
k
: infer
I
) => void ?
I
: never; /** * Convert Record to Union Type. */ type
RecordToUnion
<
T
extends
Record
<string, any>> =
T
[keyof
T
];
/** * Gets object T property by key [K]. */ type
ValueOf
<
T
extends
Record
<any, any>,
K
> =
T
[
K
];
/** * Props Map Value type. */ type
PropsMapValue
= {
allProps
:
Map
<string,
PropKind
>;
onlyProps
:
Set
<string>;
}; /** * Accessors and Methods map type. */ type
AccessorsMethodsMap
= {
accessors
:
Accessors
;
methods
:
Set
<string>;
}; export type
IVueInstanceBase
= {
init
?: (...
args
: any[]) => void;
}; /** * IVue static configuration interface. */ export interface
IVueStaticConfig
<
T
> {
ivueGlobalStore
?: boolean;
ivueCloneByReference
?:
Set
<keyof
T
>;
ivueDisableReactivity
?:
Set
<keyof
T
>;
} /** * IVue constructor type. */ export type
IVueConstructor
<
T
> = new (...
args
: any[]) =>
T
;
/** * IVue class type with static configuration. */ export type
IVueClass
<
T
> =
IVueConstructor
<
T
> &
IVueStaticConfig
<
T
>;
/** * Get IVue init() method arguments IVueInitArgs<T> */ export type
IVueInitArgs
<
T
extends
IVueInstanceBase
> =
T
['init'] extends (
...
args
: any[]
) => any ?
Parameters
<
T
['init']>
: never; /** * Get Interface's method keys MethodKeys<T> */ type
MethodKeys
<
T
> = {
[
P
in keyof
T
]:
T
[
P
] extends (...
args
: any[]) => any ?
P
: never;
}[keyof
T
];
/** * Get Interface's property's function arguments Parameters<F> */ export type
IFnParameters
<
T
extends
Record
<any, any> |
InstanceType
<
AnyClass
>,
K
extends
MethodKeys
<
T
>
> =
Parameters
<
Extract
<
Required
<
Pick
<
T
,
K
>>[
K
],
AnyFn
>>;
/** * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K] */ export type
IFnParameter
<
T
extends
Record
<any, any>,
P
extends keyof
T
,
K
extends number
> =
FnParameter
<
ValueOf
<
T
,
P
>,
K
>;
export type
IVueDeepCloneArgsMap
=
Map
<
AnyClass
, any[]>;
type
CloneArgsFor
<
T
> =
T
extends {
init
: (
isClone
: boolean, ...
rest
: infer
A
) => any;
} ?
A
: []; /** Types End. */ export const
IVUE_INSTANCE_SYMBOL
=
Symbol
('IVUE_INSTANCE');
/** * Stores accessors for each class prototype processed by * @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection * of unused class accessors, ensuring memory efficiency in long-running applications. */ export let
accessorsMethodsMaps
= new
WeakMap
<object,
AccessorsMethodsMap
>();
/** * Get accessors of an entire class prototype ancestors chain as a Map. * Completely emulates JavaScript class inheritance chain for getters and setters. * * @param className * @return {AccessorsMethodsMap} */ const
getClassAccessorsMethodsMap
= (
className
:
AnyClass
):
AccessorsMethodsMap
=> {
if (
accessorsMethodsMaps
.
has
(
className
)) {
return
accessorsMethodsMaps
.
get
(
className
) as
AccessorsMethodsMap
;
} const
savedAccessors
:
Accessors
= new
Map
();
const
savedMethods
:
Set
<string> = new
Set
();
let
prototype
=
className
.
prototype
;
while (
prototype
&&
prototype
!==
Object
.
prototype
) {
const
accessors
=
getOwnPropertyDescriptors
(
prototype
);
for (const
propertyName
in
accessors
) {
if (
propertyName
=== 'constructor') continue;
const
descriptor
=
accessors
[
propertyName
];
if (
descriptor
.
get
||
descriptor
.
set
) {
// Only add if it hasn't been defined yet (i.e. subclass overrides win) if (!
savedAccessors
.
has
(
propertyName
)) {
savedAccessors
.
set
(
propertyName
,
descriptor
);
} } else if (typeof
descriptor
.
value
=== 'function') {
savedMethods
.
add
(
propertyName
);
} }
prototype
=
getPrototypeOf
(
prototype
);
} const
propertyMap
= {
accessors
:
savedAccessors
,
methods
:
savedMethods
,
};
accessorsMethodsMaps
.
set
(
className
,
propertyMap
);
return
propertyMap
;
}; /** * Property kind enum for distinguishing between data properties and accessors. */ const enum
PropKind
{
Data
= 0,
Accessor
= 2,
} /** * Stores properties for each class prototype processed by * @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection * of unused class properties, ensuring memory efficiency in long-running applications. */ export let
propertiesAccessorsMaps
:
WeakMap
<object,
PropsMapValue
> =
new
WeakMap
();
/** * 'caller', 'callee', 'arguments', 'constructor' are * special object properties, so should be skipped. * Also skip 'toRefs' as it's added by ivue and not part of the class itself. */ const
skipProps
= new
Set
(['caller', 'callee', 'arguments', 'constructor']);
/** * Get properties of an entire class prototype ancestors chain as a Map. */ const
getClassPropertiesAccessorsMap
= (
obj
: object):
PropsMapValue
=> {
const
constructor
=
obj
.
constructor
;
/* Retrieve props from cache. */ if (
propertiesAccessorsMaps
.
has
(
constructor
)) {
return
propertiesAccessorsMaps
.
get
(
constructor
) as
PropsMapValue
;
} const {
accessors
,
methods
} =
accessorsMethodsMaps
.
get
(
constructor
) ??
getClassAccessorsMethodsMap
(
constructor
as
AnyClass
);
const
onlyProps
= new
Set
<string>();
const
allProps
:
Map
<string,
PropKind
> = new
Map
();
do { const
propertyNames
=
getOwnPropertyNames
(
obj
);
for (let
i
= 0,
j
=
propertyNames
.
length
;
i
<
j
;
i
++) {
const
property
=
propertyNames
[
i
];
if (
skipProps
.
has
(
property
)) continue; // Skip special properties
if (
allProps
.
has
(
property
)) continue; // Already defined in subclass, skip it
if (
accessors
.
has
(
property
)) {
allProps
.
set
(
property
,
PropKind
.
Accessor
);
} else if (!
methods
.
has
(
property
)) {
onlyProps
.
add
(
property
);
allProps
.
set
(
property
,
PropKind
.
Data
);
} }
obj
=
getPrototypeOf
(
obj
);
} while (
obj
&&
obj
.
constructor
!==
Object
);
const
result
= {
allProps
,
onlyProps
};
propertiesAccessorsMaps
.
set
(
constructor
,
result
);
return
result
;
}; /** * Infinite Vue (ivue) class reactive initializer. * * Converts class instance to a reactive object, * where accessors are converted to computeds. * * You can turn off computed behaviour by adding static * ivue object and setting the getter props to false. * class ClassName { * static ivue = { * getter: false * } * // .getter -> will be a standard JS non-computed getter * get getter () { return 'hello world'; } * } * * @param className Any Class * @param args Class constructor arguments that you would pass to a `new AnyClass(args...)` * @returns {IVue<T>} */ export const
ivue
= <
T
extends
AnyClass
>(
className
:
T
,
...
args
:
InferredArgs
<
T
>
):
IVue
<
T
> => {
const {
accessors
,
methods
} =
getClassAccessorsMethodsMap
(
className
);
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
/** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
console
.
log
('des criptor',
prop
,
descriptor
);
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } // In ivue(), for method binding: const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; /** Bind all methods to the vue instance. */ for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(false);
return
vue
;
}; const
ivueClone
= <
T
extends
AnyClass
>(
className
:
T
,
args
:
InferredArgs
<
T
>,
vueSrc
:
IVue
<
T
>,
accessors
:
Accessors
,
methods
:
Set
<string>,
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>> = [] as
CloneArgsFor
<
InstanceType
<
T
>>
):
IVue
<
T
> => {
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
const {
onlyProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** Clone properties. */ const
cloneByRef
= (
className
as any).ivueCloneByReference;
const
deepCloneArgs
=
activeDeepCloneArgsMap
.
get
(
className
);
if (
cloneByRef
) {
for (const
prop
of
onlyProps
) {
vue
[
prop
] =
cloneByRef
.has(
prop
)
?
vueSrc
[
prop
]
:
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } else { for (const
prop
of
onlyProps
) {
vue
[
prop
] =
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } /** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } /** Bind all methods to the vue instance. */ const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(true, ...
cloneArgs
);
return
vue
;
}; /** * `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value * `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} Ref but with type unwrapped */ export const
iref
=
ref
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value * `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} ShallowRef but with type unwrapped */ export const
ishallowRef
=
shallowRef
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types. * * It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value. * * @param obj Any Vue 3 composable return type or any object or any type * @returns {Use<T>} Composable with types unwrapped */ export const
iuse
= <
T
extends object>(
obj
:
T
):
Use
<
T
> =>
obj
as unknown as
Use
<
T
>; /** Unwrap any other Object or any type down to its bare types. */
/** * Convert reactive ivue class to Vue 3 refs. * * @param vue @see IVue * @param accessors @see Accessors * @param computeds @see Computeds * @returns {ExtendWithToRefs<T>['toRefs']} */ const
ivueToRefs
= <
T
extends
AnyClass
>(
vue
:
IVue
<
T
>,
accessors
:
Accessors
,
computeds
:
Computeds
):
IVueToRefsFn
<
T
> => {
/** Caches for toRefs function */ /** Cached accessor function */ const
getAccessor
=
accessors
.
get
.
bind
(
accessors
);
/** Get all class properties */ const {
allProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** The actual toRefs function returned. */ return function (
props
: (string & keyof
InstanceType
<
T
>)[] | boolean = false,
unwrap
?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
): any /** @see {ReturnType<IVueToRefsFn<T>>} */ { /** Resulting refs store. */ const
result
:
Record
<string, any> = {};
/** Convert all props to refs and leave functions as is. */ if (
isArray
(
props
)) {
for (const
prop
of
props
) {
const
kind
=
allProps
.
get
(
prop
);
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } else { for (const [
prop
,
kind
] of
allProps
.
entries
()) {
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } return
result
as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
}; }; /** * Resolve property to ref, method or computed and assign it to result. * @private */ const
resolveIvueToRefs
= (
result
:
Record
<string, any>,
prop
: string,
kind
:
PropKind
| undefined,
vue
:
IVue
<any>,
getAccessor
:
Accessors
['get'],
computeds
:
Record
<string,
ComputedRef
<any>>
): void => { if (
kind
===
PropKind
.
Accessor
) {
const
descriptor
=
getAccessor
(
prop
) as PropertyDescriptor;
result
[
prop
] =
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)); } else if (
kind
===
PropKind
.
Data
) {
result
[
prop
] =
toRef
(
vue
,
prop
);
} }; /** * Vue props interface in defineComponent() style. */ export type
VuePropsObject
=
Record
<
string, {
type
: any;
default
?: any;
required
?: boolean }
>; /** * Vue Props with default properties declared as existing and having values. */ export type
VuePropsWithDefaults
<
T
extends
VuePropsObject
> = {
[
K
in keyof
T
]: {
type
:
T
[
K
]['type'];
default
:
T
[
K
]['default'];
required
?: boolean;
}; }; /** * Determines if the value is a JavaScript Class. * Note that class is a class function in JavaScript. * * @param val Any value * @returns boolean If it's a JavaScript Class returns true */ export const
isClass
= (
val
: any): boolean => {
if (typeof
val
!== 'function') return false; // Not a function, so not a class function either
if (!
val
.prototype) return false; // Arrow function, so not a class
// Finally -> distinguish between a normal function and a class function if (
getOwnPropertyDescriptor
(
val
, 'prototype')?.
writable
) {
// Has writable prototype return false; // Normal function } else { return true; // Class -> Not a function } }; /** * Creates props with defaults in defineComponent() style. * * Merge defaults regular object with Vue types object * declared in defineComponent() style. * * This is made so that the defaults can be declared "as they are" * without requiring objects to be function callbacks returning an object. * * // You don't need to wrap objects in () => ({ nest: { nest :{} } }) * // You can just delcare them normally. * const defaults = { * nest: { * nest * } * } * * This function will create the Vue expected callbacks for Objects, Arrays & Classes * but leave primitive properties and functions intact so that * the final object is fully defineComponent() style compatible. * * @param defaults Regular object of default key -> values * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default * @returns Props declared in defineComponent() style with all properties having default property declared. */ export const
propsWithDefaults
= <
T
extends
VuePropsObject
>(
defaults
:
Record
<string, any>,
typedProps
:
T
,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined =
undefined
):
VuePropsWithDefaults
<
T
> => {
for (const
prop
in
typedProps
) {
const
def
=
defaults
?.[
prop
];
const
typed
=
typedProps
[
prop
];
/** Skip if prop is required — Vue will enforce it at runtime */ if (
typed
.
required
) continue;
/** Skip if default is undefined */ if (
def
===
undefined
) continue;
if (typeof
def
=== 'object' &&
def
!== null) {
/** Handle Arrays & Objects -> wrap them with an arrow function. */
typedProps
[
prop
].
default
= () =>
deepClone
(
def
,
deepCloneArgs
);
} else { if (
isClass
(
def
)) {
/** Handle JavaScript Classes -> wrap them with an arrow function */
typedProps
[
prop
].
default
= () =>
def
;
} else { /** Handle JavaScript Function And All primitive properties -> output directly */
typedProps
[
prop
].
default
=
def
;
} } } return
typedProps
as
VuePropsWithDefaults
<
T
>;
}; /** * Copies own properties (including symbols and non-enumerable) from source to target. * Recursively deep clones values, but preserves getters/setters as-is. */ export const
copyOwnProps
= (
source
: any,
target
: any,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined,
seen
:
WeakMap
<object, any>
) => { // 1. Get all descriptors (String keys AND Symbol keys) const
descriptors
=
Object
.
getOwnPropertyDescriptors
(
source
);
// 2. Iterate over all keys returned by Reflect (safety for Proxies/Environments) for (const
key
of Reflect.
ownKeys
(
descriptors
)) {
const
desc
=
descriptors
[
key
as any];
// 3. If it is a data descriptor (has a value), we must deep clone the value. // We do NOT clone getters/setters (we copy the function reference). if ('value' in
desc
) {
desc
.
value
=
deepClone
(
desc
.
value
,
deepCloneArgs
,
seen
);
} // 4. Define on target
Object
.
defineProperty
(
target
,
key
,
desc
);
} }; export let
activeDeepCloneArgsMap
= new
WeakMap
<
AnyClass
,
IVueDeepCloneArgsMap
>(); export const
deepClone
= (
source
: any,
deepCloneArgs
?:
IVueDeepCloneArgsMap
,
seen
:
WeakMap
<object, any> = new
WeakMap
()
): any => { // --- TIER 1: The "Every Call" Checks (Fastest) --- if (
source
== null || typeof
source
!== 'object') return
source
;
if (
seen
.
has
(
source
)) return
seen
.
get
(
source
);
// --- TIER 2: The "Hottest" Collection (Arrays) --- if (
isArray
(
source
)) {
const
len
=
source
.
length
;
const
out
= new
Array
(
len
);
seen
.
set
(
source
,
out
);
for (let
i
= 0;
i
<
len
;
i
++) {
out
[
i
] =
deepClone
(
source
[
i
],
deepCloneArgs
,
seen
);
} return
out
;
} // --- TIER 3: The "Hottest" Objects (Plain Objects) --- const
prototype
=
getPrototypeOf
(
source
);
if (
prototype
===
Object
.
prototype
||
prototype
=== null) {
const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} // --- TIER 4: IVue Special Logic (Your Framework) --- // Checked here because it's specific to your domain if (
source
[
IVUE_INSTANCE_SYMBOL
]) {
const
srcClass
=
source
.constructor as
IVueClass
<any>;
if (
srcClass
.
ivueGlobalStore
) {
seen
.
set
(
source
,
source
);
return
source
;
} let
out
;
if (
deepCloneArgs
?.
has
(
srcClass
)) {
activeDeepCloneArgsMap
.
set
(
srcClass
,
deepCloneArgs
);
try {
out
=
source
.clone(...(
deepCloneArgs
.
get
(
srcClass
) ?? []));
} finally {
activeDeepCloneArgsMap
.
delete
(
srcClass
);
} } else {
out
=
source
.clone();
}
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 5: Common Built-ins (Sorted by Likelihood) --- // instanceof is fast, but order matters slightly for CPU branch prediction if (
source
instanceof
Date
) {
return new
Date
(
source
.
getTime
());
} if (
source
instanceof
Map
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const [
k
,
v
] of
source
) {
out
.set(
deepClone
(
k
,
deepCloneArgs
,
seen
),
deepClone
(
v
,
deepCloneArgs
,
seen
)
); } return
out
;
} if (
source
instanceof
Set
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const
v
of
source
) {
out
.add(
deepClone
(
v
,
deepCloneArgs
,
seen
));
} return
out
;
} // --- TIER 6: Rare / Heavy Objects (The "Slow" Tail) --- if (
source
instanceof
RegExp
) {
const
out
= new
RegExp
(
source
.
source
,
source
.
flags
);
out
.
lastIndex
=
source
.
lastIndex
;
return
out
;
} if (
source
instanceof
Error
) {
const
out
= new (
source
.
constructor
as any)(
source
.
message
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} if (
source
instanceof
Promise
||
source
instanceof
WeakMap
||
source
instanceof
WeakSet
) {
seen
.
set
(
source
,
source
);
return
source
;
} if (
source
instanceof
DataView
) {
// Clone the underlying buffer to ensure deep copy const
out
= new
DataView
(
source
.
buffer
.
slice
(0),
source
.
byteOffset
,
source
.
byteLength
);
seen
.
set
(
source
,
out
);
return
out
;
} // Binary data (less common in UI state, but supported) if (
ArrayBuffer
.
isView
(
source
) && !(
source
instanceof
DataView
)) {
const
out
= new (
source
.
constructor
as any)(
source
);
seen
.
set
(
source
,
out
);
return
out
;
} if (
source
instanceof
ArrayBuffer
) {
const
out
=
source
.
slice
(0);
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 7: Final Fallback (Custom User Classes) --- // If it's a class instance not handled above const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
}; /** * Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps. * Useful for SSR scenarios where you want to reset the cache between requests. * Also useful for testing purposes to ensure a clean state. */ export const
clearCache
= () => {
accessorsMethodsMaps
= new
WeakMap
();
propertiesAccessorsMaps
= new
WeakMap
();
activeDeepCloneArgsMap
= new
WeakMap
();
}; /** Exported for testing purposes. */ export const
__test__
= {
copyOwnProps
,
getClassAccessorsMethodsMap
,
getClassPropertiesAccessorsMap
,
}; /** Necessary ivue.ts to be treated as a module. */ export {};
ts
import { 
ref
,
onMounted
,
onUnmounted
} from 'vue';
export function
useMouse
() {
const
x
=
ref
(0);
const
y
=
ref
(0);
const
sourceType
=
ref
<'mouse' | 'touch'>('mouse');
function
updateMouse
(
event
: MouseEvent) {
sourceType
.
value
= 'mouse';
x
.
value
=
event
.
clientX
;
y
.
value
=
event
.
clientY
;
} function
updateTouch
(
event
: TouchEvent) {
sourceType
.
value
= 'touch';
if (
event
.
touches
.
length
> 0) {
const
touch
=
event
.
touches
[0];
x
.
value
=
touch
.
clientX
;
y
.
value
=
touch
.
clientY
;
} }
onMounted
(() => {
window
.
addEventListener
('mousemove',
updateMouse
);
window
.
addEventListener
('touchstart',
updateTouch
);
window
.
addEventListener
('touchmove',
updateTouch
);
});
onUnmounted
(() => {
window
.
removeEventListener
('mousemove',
updateMouse
);
window
.
removeEventListener
('touchstart',
updateTouch
);
window
.
removeEventListener
('touchmove',
updateTouch
);
}); return {
x
,
y
,
sourceType
,
}; }
ts
import { 
defineProps
,
defineEmits
,
onMounted
,
watch
} from 'vue';
import { type
Use
,
ivue
,
iref
,
iuse
} from 'ivue';
import {
useMouse
} from './useMouse';
// Properly declared unwrapped composable. type
UseMouse
=
Use
<typeof
useMouse
>;
interface CounterProps {
initialCount
: number;
} interface CounterEmits { (
e
: 'increment',
count
: number): void;
} const
props
=
defineProps
<CounterProps>();
const
emit
=
defineEmits
<CounterEmits>();
/** * Example of a properly defined ivue class. */ class
Counter
{
// Properly declared unwrapped composable.
mouse
:
UseMouse
;
// Properly declared DOM Ref.
spanElementRef
=
iref
<HTMLElement | null>(null);
constructor(public
props
: CounterProps, public
emit
: CounterEmits) {
// Properly declared auto-unwrapped composable. this.
mouse
=
iuse
(
useMouse
);
} /** * Properly declared ivue init() function * used to initialize Vue lifecycle hooks. */
init
() {
// Properly set lifecycle hook.
onMounted
(() => {
this.
count
= 4;
}); // Properly set watch function.
watch
(
() => this.
count
,
(
newCount
: number) => {
if (
newCount
=== 5) {
alert
('You reached the count of ' +
newCount
+ '!');
} } ); }
count
=
iref
(0); // Properly declared Ref with auto inferred type -> number
timesClicked
=
iref
(0); // Properly declared Ref with auto inferred type -> number
// Properly declared function (not arrow function).
increment
() {
this.
count
++;
} // Properly declared function (not arrow function).
click
() {
this.
increment
();
this.
timesClicked
++;
} // Properly declared computed getter. get
doubleCount
() {
return this.
count
* 2;
} } // Properly initialized ivue class. const
counter
=
ivue
(
Counter
,
props
,
emit
);
counter
.
count
;
ts
import { 
ref
,
onMounted
,
onUnmounted
} from 'vue';
export function
useMouse
() {
const
x
=
ref
(0);
const
y
=
ref
(0);
const
sourceType
=
ref
<'mouse' | 'touch'>('mouse');
function
updateMouse
(
event
: MouseEvent) {
sourceType
.
value
= 'mouse';
x
.
value
=
event
.
clientX
;
y
.
value
=
event
.
clientY
;
} function
updateTouch
(
event
: TouchEvent) {
sourceType
.
value
= 'touch';
if (
event
.
touches
.
length
> 0) {
const
touch
=
event
.
touches
[0];
x
.
value
=
touch
.
clientX
;
y
.
value
=
touch
.
clientY
;
} }
onMounted
(() => {
window
.
addEventListener
('mousemove',
updateMouse
);
window
.
addEventListener
('touchstart',
updateTouch
);
window
.
addEventListener
('touchmove',
updateTouch
);
});
onUnmounted
(() => {
window
.
removeEventListener
('mousemove',
updateMouse
);
window
.
removeEventListener
('touchstart',
updateTouch
);
window
.
removeEventListener
('touchmove',
updateTouch
);
}); return {
x
,
y
,
sourceType
,
}; }
ts
import type { 
ComputedRef
,
ExtractPropTypes
,
Ref
,
ToRef
} from 'vue';
import {
computed
,
markRaw
,
reactive
,
ref
,
shallowRef
,
toRef
} from 'vue';
import type {
Ref
as
DemiRef
} from 'vue-demi';
/** Cached global methods. */ const
isArray
=
Array
.
isArray
;
const
createObject
=
Object
.
create
;
const
defineProperty
=
Object
.
defineProperty
;
const
getPrototypeOf
=
Object
.
getPrototypeOf
;
const
getOwnPropertyNames
=
Object
.
getOwnPropertyNames
;
const
getOwnPropertyDescriptor
=
Object
.
getOwnPropertyDescriptor
;
const
getOwnPropertyDescriptors
=
Object
.
getOwnPropertyDescriptors
;
/** Types */ /** * IVue core reactive instance type with an extended .toRefs() method added. */ export type
IVue
<
T
extends
AnyClass
> =
InstanceType
<
T
> & {
toRefs
:
IVueToRefsFn
<
T
>;
clone
:
IVueClone
<
T
>;
}; /** * Type definition for `.clone()` method creates a deep clone of the ivue instance. */ export type
IVueClone
<
T
extends
AnyClass
> = (
...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>
) =>
IVue
<
T
>;
/** * Type definition for `.toRefs()` method converts reactive class properties to composable .value properties. * But if props = true OR unwrap = true is specified, the refs will be unwrapped refs to be able to be merged with the the root class properties without losing reactivity. */ /** Extract only non-method keys from a type */ type
NonMethodKeys
<
T
> = {
[
K
in keyof
T
]:
T
[
K
] extends
AnyFn
? never :
K
;
}[keyof
T
];
/** Create a type with only data properties and accessors */ type
DataProperties
<
T
> =
Pick
<
T
,
NonMethodKeys
<
T
>>;
/** * Simplified IVueToRefsFn that only works with data properties */ interface
IVueToRefsFn
<
T
extends
AnyClass
> {
<
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[]):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
IVueRefs
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: false):
Pick
<
IVueRefs
<
InstanceType
<
T
>>,
P
>; <
P
extends keyof
DataProperties
<
InstanceType
<
T
>>>(
props
:
P
[],
unwrap
: true
):
Pick
<
DataProperties
<
InstanceType
<
T
>>,
P
>;
(
props
: true):
DataProperties
<
InstanceType
<
T
>>;
(
props
: false):
IVueRefs
<
InstanceType
<
T
>>;
():
IVueRefs
<
InstanceType
<
T
>>;
} /** * Converts a class properties to a composable .value Refs using ToRef vue type, * also knows NOT to convert functions to .value Refs but to leave them as is. */ export type
IVueRefs
<
T
> = {
[
K
in keyof
T
as
T
[
K
] extends
AnyFn
? never :
K
]:
ToRef
<
T
[
K
]>;
}; /** * Unwraps Ref Recursively, helps resolve fully the bare value types of any type T * because sometimes the Refs are returned as they are with `.value` from computeds, * thus inferring nested ComputedRef<ComputedRef<ComputedRef<Ref>>> types, which * are difficult to fully resolve to bare values without this utility. * Also support Vue 3 Demi for vue-use library support. */ type
UnwrapRefRecursively
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
['value']>
:
T
;
/** * Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types. */ type
UnwrapComposableRefs
<
T
> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
: { [
K
in keyof
T
]:
T
[
K
] extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
[
K
]>
:
T
[
K
];
}; /** * Fully unwraps to bare value types any Vue 3 composable return definition type. */ export type
Use
<
T
= any> =
T
extends
Ref
|
DemiRef
?
UnwrapRefRecursively
<
T
>
:
UnwrapComposableRefs
<
T
extends
AnyFn
?
ReturnType
<
T
> :
T
>;
/** * Extracts object defined emit types by converting them to a plain interface. */ export type
ExtractEmitTypes
<
T
extends
Record
<string, any>> =
UnionToIntersection
<
RecordToUnion
<{
[
K
in keyof
T
]: (
evt
:
K
, ...
args
:
Parameters
<
T
[
K
]>) => void;
}> >; /** * Extract properties as all assigned properties because they have defaults. */ export type
ExtractPropDefaultTypes
<
O
> = {
[
K
in keyof
O
]:
ValueOf
<
ExtractPropTypes
<
O
>,
K
>;
}; /** * Extend slots interface T with prefixed 'before--' & 'after--' slots to create fully extensible flexible slots. */ export type
ExtendSlots
<
T
> =
PrefixKeys
<
T
, 'before--'> &
T
&
PrefixKeys
<
T
, 'after--'>;
/** Helper Types. */ /** * Any JavaScript function of any type. */ export type
AnyFn
= (...
args
: any[]) => any;
/** * Any JavaScript class of any type. */ export type
AnyClass
= new (...
args
: any[]) => any;
/** * Accessors map type. */ type
Accessors
=
Map
<string, PropertyDescriptor>;
/** * Computeds hash map type. */ type
Computeds
=
Record
<string,
ComputedRef
>;
/** * Infer paramaters of a constructor function of a Class. */ export type
InferredArgs
<
T
> =
T
extends { new (...
args
: infer
P
): any }
?
P
: never[]; /** * Prefix keys of an interface T with a prefix P. */ export type
PrefixKeys
<
T
,
P
extends string | undefined = undefined> = {
[
K
in
Extract
<keyof
T
, string> as
P
extends string ? `${
P
}${
K
}` :
K
]:
T
[
K
];
}; /** * Get function arguments Parmeters<F> parameter by key K */ export type
FnParameter
<
F
extends
AnyFn
,
K
extends number> =
Parameters
<
F
>[
K
];
/** * Convert Union Type to Intersection Type. */ type
UnionToIntersection
<
U
> = (
U
extends any ? (
k
:
U
) => void : never) extends (
k
: infer
I
) => void ?
I
: never; /** * Convert Record to Union Type. */ type
RecordToUnion
<
T
extends
Record
<string, any>> =
T
[keyof
T
];
/** * Gets object T property by key [K]. */ type
ValueOf
<
T
extends
Record
<any, any>,
K
> =
T
[
K
];
/** * Props Map Value type. */ type
PropsMapValue
= {
allProps
:
Map
<string,
PropKind
>;
onlyProps
:
Set
<string>;
}; /** * Accessors and Methods map type. */ type
AccessorsMethodsMap
= {
accessors
:
Accessors
;
methods
:
Set
<string>;
}; export type
IVueInstanceBase
= {
init
?: (...
args
: any[]) => void;
}; /** * IVue static configuration interface. */ export interface
IVueStaticConfig
<
T
> {
ivueGlobalStore
?: boolean;
ivueCloneByReference
?:
Set
<keyof
T
>;
ivueDisableReactivity
?:
Set
<keyof
T
>;
} /** * IVue constructor type. */ export type
IVueConstructor
<
T
> = new (...
args
: any[]) =>
T
;
/** * IVue class type with static configuration. */ export type
IVueClass
<
T
> =
IVueConstructor
<
T
> &
IVueStaticConfig
<
T
>;
/** * Get IVue init() method arguments IVueInitArgs<T> */ export type
IVueInitArgs
<
T
extends
IVueInstanceBase
> =
T
['init'] extends (
...
args
: any[]
) => any ?
Parameters
<
T
['init']>
: never; /** * Get Interface's method keys MethodKeys<T> */ type
MethodKeys
<
T
> = {
[
P
in keyof
T
]:
T
[
P
] extends (...
args
: any[]) => any ?
P
: never;
}[keyof
T
];
/** * Get Interface's property's function arguments Parameters<F> */ export type
IFnParameters
<
T
extends
Record
<any, any> |
InstanceType
<
AnyClass
>,
K
extends
MethodKeys
<
T
>
> =
Parameters
<
Extract
<
Required
<
Pick
<
T
,
K
>>[
K
],
AnyFn
>>;
/** * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K] */ export type
IFnParameter
<
T
extends
Record
<any, any>,
P
extends keyof
T
,
K
extends number
> =
FnParameter
<
ValueOf
<
T
,
P
>,
K
>;
export type
IVueDeepCloneArgsMap
=
Map
<
AnyClass
, any[]>;
type
CloneArgsFor
<
T
> =
T
extends {
init
: (
isClone
: boolean, ...
rest
: infer
A
) => any;
} ?
A
: []; /** Types End. */ export const
IVUE_INSTANCE_SYMBOL
=
Symbol
('IVUE_INSTANCE');
/** * Stores accessors for each class prototype processed by * @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection * of unused class accessors, ensuring memory efficiency in long-running applications. */ export let
accessorsMethodsMaps
= new
WeakMap
<object,
AccessorsMethodsMap
>();
/** * Get accessors of an entire class prototype ancestors chain as a Map. * Completely emulates JavaScript class inheritance chain for getters and setters. * * @param className * @return {AccessorsMethodsMap} */ const
getClassAccessorsMethodsMap
= (
className
:
AnyClass
):
AccessorsMethodsMap
=> {
if (
accessorsMethodsMaps
.
has
(
className
)) {
return
accessorsMethodsMaps
.
get
(
className
) as
AccessorsMethodsMap
;
} const
savedAccessors
:
Accessors
= new
Map
();
const
savedMethods
:
Set
<string> = new
Set
();
let
prototype
=
className
.
prototype
;
while (
prototype
&&
prototype
!==
Object
.
prototype
) {
const
accessors
=
getOwnPropertyDescriptors
(
prototype
);
for (const
propertyName
in
accessors
) {
if (
propertyName
=== 'constructor') continue;
const
descriptor
=
accessors
[
propertyName
];
if (
descriptor
.
get
||
descriptor
.
set
) {
// Only add if it hasn't been defined yet (i.e. subclass overrides win) if (!
savedAccessors
.
has
(
propertyName
)) {
savedAccessors
.
set
(
propertyName
,
descriptor
);
} } else if (typeof
descriptor
.
value
=== 'function') {
savedMethods
.
add
(
propertyName
);
} }
prototype
=
getPrototypeOf
(
prototype
);
} const
propertyMap
= {
accessors
:
savedAccessors
,
methods
:
savedMethods
,
};
accessorsMethodsMaps
.
set
(
className
,
propertyMap
);
return
propertyMap
;
}; /** * Property kind enum for distinguishing between data properties and accessors. */ const enum
PropKind
{
Data
= 0,
Accessor
= 2,
} /** * Stores properties for each class prototype processed by * @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection * of unused class properties, ensuring memory efficiency in long-running applications. */ export let
propertiesAccessorsMaps
:
WeakMap
<object,
PropsMapValue
> =
new
WeakMap
();
/** * 'caller', 'callee', 'arguments', 'constructor' are * special object properties, so should be skipped. * Also skip 'toRefs' as it's added by ivue and not part of the class itself. */ const
skipProps
= new
Set
(['caller', 'callee', 'arguments', 'constructor']);
/** * Get properties of an entire class prototype ancestors chain as a Map. */ const
getClassPropertiesAccessorsMap
= (
obj
: object):
PropsMapValue
=> {
const
constructor
=
obj
.
constructor
;
/* Retrieve props from cache. */ if (
propertiesAccessorsMaps
.
has
(
constructor
)) {
return
propertiesAccessorsMaps
.
get
(
constructor
) as
PropsMapValue
;
} const {
accessors
,
methods
} =
accessorsMethodsMaps
.
get
(
constructor
) ??
getClassAccessorsMethodsMap
(
constructor
as
AnyClass
);
const
onlyProps
= new
Set
<string>();
const
allProps
:
Map
<string,
PropKind
> = new
Map
();
do { const
propertyNames
=
getOwnPropertyNames
(
obj
);
for (let
i
= 0,
j
=
propertyNames
.
length
;
i
<
j
;
i
++) {
const
property
=
propertyNames
[
i
];
if (
skipProps
.
has
(
property
)) continue; // Skip special properties
if (
allProps
.
has
(
property
)) continue; // Already defined in subclass, skip it
if (
accessors
.
has
(
property
)) {
allProps
.
set
(
property
,
PropKind
.
Accessor
);
} else if (!
methods
.
has
(
property
)) {
onlyProps
.
add
(
property
);
allProps
.
set
(
property
,
PropKind
.
Data
);
} }
obj
=
getPrototypeOf
(
obj
);
} while (
obj
&&
obj
.
constructor
!==
Object
);
const
result
= {
allProps
,
onlyProps
};
propertiesAccessorsMaps
.
set
(
constructor
,
result
);
return
result
;
}; /** * Infinite Vue (ivue) class reactive initializer. * * Converts class instance to a reactive object, * where accessors are converted to computeds. * * You can turn off computed behaviour by adding static * ivue object and setting the getter props to false. * class ClassName { * static ivue = { * getter: false * } * // .getter -> will be a standard JS non-computed getter * get getter () { return 'hello world'; } * } * * @param className Any Class * @param args Class constructor arguments that you would pass to a `new AnyClass(args...)` * @returns {IVue<T>} */ export const
ivue
= <
T
extends
AnyClass
>(
className
:
T
,
...
args
:
InferredArgs
<
T
>
):
IVue
<
T
> => {
const {
accessors
,
methods
} =
getClassAccessorsMethodsMap
(
className
);
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
/** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
console
.
log
('des criptor',
prop
,
descriptor
);
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } // In ivue(), for method binding: const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; /** Bind all methods to the vue instance. */ for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(false);
return
vue
;
}; const
ivueClone
= <
T
extends
AnyClass
>(
className
:
T
,
args
:
InferredArgs
<
T
>,
vueSrc
:
IVue
<
T
>,
accessors
:
Accessors
,
methods
:
Set
<string>,
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>> = [] as
CloneArgsFor
<
InstanceType
<
T
>>
):
IVue
<
T
> => {
const
computeds
:
Computeds
| any =
accessors
?.
size
?
createObject
(null)
: null; /** Create a reactive instance of the class. */ const
vue
=
reactive
(new
className
(...
args
));
const {
onlyProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** Clone properties. */ const
cloneByRef
= (
className
as any).ivueCloneByReference;
const
deepCloneArgs
=
activeDeepCloneArgsMap
.
get
(
className
);
if (
cloneByRef
) {
for (const
prop
of
onlyProps
) {
vue
[
prop
] =
cloneByRef
.has(
prop
)
?
vueSrc
[
prop
]
:
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } else { for (const
prop
of
onlyProps
) {
vue
[
prop
] =
deepClone
(
vueSrc
[
prop
],
deepCloneArgs
);
} } /** Setup accessors as computeds. */ for (const [
prop
,
descriptor
] of
accessors
) {
/** Convert descriptor to computed. */
defineProperty
(
vue
,
prop
, {
get
: () =>
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } /** Disable reactivity for specified properties. */ const
nonReactiveProps
= (
className
as any)?.ivueDisableReactivity;
if (
nonReactiveProps
) {
for (const
prop
of
nonReactiveProps
) {
const
descriptor
=
accessors
.
get
(
prop
);
if (
descriptor
) {
defineProperty
(
vue
,
prop
, {
get
:
descriptor
.
get
?.
bind
(
vue
),
set
:
descriptor
.
set
?.
bind
(
vue
),
enumerable
:
descriptor
.
enumerable
,
configurable
:
descriptor
.
configurable
,
}); } else {
vue
[
prop
] =
markRaw
(
vue
[
prop
]);
} } } /** Bind all methods to the vue instance. */ const
methodDescriptor
: PropertyDescriptor = {
writable
: true,
configurable
: true,
enumerable
: false,
}; for (const
methodName
of
methods
) {
methodDescriptor
.
value
=
vue
[
methodName
].bind(
vue
);
defineProperty
(
vue
,
methodName
,
methodDescriptor
);
} /** Define .toRefs() method on the vue instance. */
defineProperty
(
vue
, 'toRefs', {
get
: (() => {
/** Lazy loaded toRefs function cache. */ let
toRefsFn
:
IVueToRefsFn
<
T
> | null = null;
return () =>
toRefsFn
?? (
toRefsFn
=
ivueToRefs
(
vue
,
accessors
,
computeds
));
})(),
enumerable
: false,
}); /** Define .clone() method on the vue instance. */
defineProperty
(
vue
, 'clone', {
value
: (...
cloneArgs
:
CloneArgsFor
<
InstanceType
<
T
>>) =>
ivueClone
(
className
,
args
,
vue
,
accessors
,
methods
,
cloneArgs
),
enumerable
: false,
}); /** Mark as ivue instance */
defineProperty
(
vue
,
IVUE_INSTANCE_SYMBOL
, {
value
: true,
enumerable
: false,
configurable
: false,
writable
: false,
}); /** Run ivue .init() initializer method, if it exists in the class. */
vue
?.init?.(true, ...
cloneArgs
);
return
vue
;
}; /** * `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value * `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} Ref but with type unwrapped */ export const
iref
=
ref
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value * `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value. * @param val T * @returns {T} ShallowRef but with type unwrapped */ export const
ishallowRef
=
shallowRef
as <
T
= any>(
value
?:
T
) =>
T
;
/** * `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types. * * It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value. * * @param obj Any Vue 3 composable return type or any object or any type * @returns {Use<T>} Composable with types unwrapped */ export const
iuse
= <
T
extends object>(
obj
:
T
):
Use
<
T
> =>
obj
as unknown as
Use
<
T
>; /** Unwrap any other Object or any type down to its bare types. */
/** * Convert reactive ivue class to Vue 3 refs. * * @param vue @see IVue * @param accessors @see Accessors * @param computeds @see Computeds * @returns {ExtendWithToRefs<T>['toRefs']} */ const
ivueToRefs
= <
T
extends
AnyClass
>(
vue
:
IVue
<
T
>,
accessors
:
Accessors
,
computeds
:
Computeds
):
IVueToRefsFn
<
T
> => {
/** Caches for toRefs function */ /** Cached accessor function */ const
getAccessor
=
accessors
.
get
.
bind
(
accessors
);
/** Get all class properties */ const {
allProps
} =
getClassPropertiesAccessorsMap
(
vue
);
/** The actual toRefs function returned. */ return function (
props
: (string & keyof
InstanceType
<
T
>)[] | boolean = false,
unwrap
?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
): any /** @see {ReturnType<IVueToRefsFn<T>>} */ { /** Resulting refs store. */ const
result
:
Record
<string, any> = {};
/** Convert all props to refs and leave functions as is. */ if (
isArray
(
props
)) {
for (const
prop
of
props
) {
const
kind
=
allProps
.
get
(
prop
);
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } else { for (const [
prop
,
kind
] of
allProps
.
entries
()) {
resolveIvueToRefs
(
result
,
prop
,
kind
,
vue
,
getAccessor
,
computeds
);
} } return
result
as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
}; }; /** * Resolve property to ref, method or computed and assign it to result. * @private */ const
resolveIvueToRefs
= (
result
:
Record
<string, any>,
prop
: string,
kind
:
PropKind
| undefined,
vue
:
IVue
<any>,
getAccessor
:
Accessors
['get'],
computeds
:
Record
<string,
ComputedRef
<any>>
): void => { if (
kind
===
PropKind
.
Accessor
) {
const
descriptor
=
getAccessor
(
prop
) as PropertyDescriptor;
result
[
prop
] =
computeds
[
prop
] ??
(
computeds
[
prop
] =
computed
({
get
:
descriptor
.
get
?.
bind
(
vue
) as unknown,
set
:
descriptor
.
set
?.
bind
(
vue
),
} as any)); } else if (
kind
===
PropKind
.
Data
) {
result
[
prop
] =
toRef
(
vue
,
prop
);
} }; /** * Vue props interface in defineComponent() style. */ export type
VuePropsObject
=
Record
<
string, {
type
: any;
default
?: any;
required
?: boolean }
>; /** * Vue Props with default properties declared as existing and having values. */ export type
VuePropsWithDefaults
<
T
extends
VuePropsObject
> = {
[
K
in keyof
T
]: {
type
:
T
[
K
]['type'];
default
:
T
[
K
]['default'];
required
?: boolean;
}; }; /** * Determines if the value is a JavaScript Class. * Note that class is a class function in JavaScript. * * @param val Any value * @returns boolean If it's a JavaScript Class returns true */ export const
isClass
= (
val
: any): boolean => {
if (typeof
val
!== 'function') return false; // Not a function, so not a class function either
if (!
val
.prototype) return false; // Arrow function, so not a class
// Finally -> distinguish between a normal function and a class function if (
getOwnPropertyDescriptor
(
val
, 'prototype')?.
writable
) {
// Has writable prototype return false; // Normal function } else { return true; // Class -> Not a function } }; /** * Creates props with defaults in defineComponent() style. * * Merge defaults regular object with Vue types object * declared in defineComponent() style. * * This is made so that the defaults can be declared "as they are" * without requiring objects to be function callbacks returning an object. * * // You don't need to wrap objects in () => ({ nest: { nest :{} } }) * // You can just delcare them normally. * const defaults = { * nest: { * nest * } * } * * This function will create the Vue expected callbacks for Objects, Arrays & Classes * but leave primitive properties and functions intact so that * the final object is fully defineComponent() style compatible. * * @param defaults Regular object of default key -> values * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default * @returns Props declared in defineComponent() style with all properties having default property declared. */ export const
propsWithDefaults
= <
T
extends
VuePropsObject
>(
defaults
:
Record
<string, any>,
typedProps
:
T
,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined =
undefined
):
VuePropsWithDefaults
<
T
> => {
for (const
prop
in
typedProps
) {
const
def
=
defaults
?.[
prop
];
const
typed
=
typedProps
[
prop
];
/** Skip if prop is required — Vue will enforce it at runtime */ if (
typed
.
required
) continue;
/** Skip if default is undefined */ if (
def
===
undefined
) continue;
if (typeof
def
=== 'object' &&
def
!== null) {
/** Handle Arrays & Objects -> wrap them with an arrow function. */
typedProps
[
prop
].
default
= () =>
deepClone
(
def
,
deepCloneArgs
);
} else { if (
isClass
(
def
)) {
/** Handle JavaScript Classes -> wrap them with an arrow function */
typedProps
[
prop
].
default
= () =>
def
;
} else { /** Handle JavaScript Function And All primitive properties -> output directly */
typedProps
[
prop
].
default
=
def
;
} } } return
typedProps
as
VuePropsWithDefaults
<
T
>;
}; /** * Copies own properties (including symbols and non-enumerable) from source to target. * Recursively deep clones values, but preserves getters/setters as-is. */ export const
copyOwnProps
= (
source
: any,
target
: any,
deepCloneArgs
:
IVueDeepCloneArgsMap
| undefined,
seen
:
WeakMap
<object, any>
) => { // 1. Get all descriptors (String keys AND Symbol keys) const
descriptors
=
Object
.
getOwnPropertyDescriptors
(
source
);
// 2. Iterate over all keys returned by Reflect (safety for Proxies/Environments) for (const
key
of Reflect.
ownKeys
(
descriptors
)) {
const
desc
=
descriptors
[
key
as any];
// 3. If it is a data descriptor (has a value), we must deep clone the value. // We do NOT clone getters/setters (we copy the function reference). if ('value' in
desc
) {
desc
.
value
=
deepClone
(
desc
.
value
,
deepCloneArgs
,
seen
);
} // 4. Define on target
Object
.
defineProperty
(
target
,
key
,
desc
);
} }; export let
activeDeepCloneArgsMap
= new
WeakMap
<
AnyClass
,
IVueDeepCloneArgsMap
>(); export const
deepClone
= (
source
: any,
deepCloneArgs
?:
IVueDeepCloneArgsMap
,
seen
:
WeakMap
<object, any> = new
WeakMap
()
): any => { // --- TIER 1: The "Every Call" Checks (Fastest) --- if (
source
== null || typeof
source
!== 'object') return
source
;
if (
seen
.
has
(
source
)) return
seen
.
get
(
source
);
// --- TIER 2: The "Hottest" Collection (Arrays) --- if (
isArray
(
source
)) {
const
len
=
source
.
length
;
const
out
= new
Array
(
len
);
seen
.
set
(
source
,
out
);
for (let
i
= 0;
i
<
len
;
i
++) {
out
[
i
] =
deepClone
(
source
[
i
],
deepCloneArgs
,
seen
);
} return
out
;
} // --- TIER 3: The "Hottest" Objects (Plain Objects) --- const
prototype
=
getPrototypeOf
(
source
);
if (
prototype
===
Object
.
prototype
||
prototype
=== null) {
const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} // --- TIER 4: IVue Special Logic (Your Framework) --- // Checked here because it's specific to your domain if (
source
[
IVUE_INSTANCE_SYMBOL
]) {
const
srcClass
=
source
.constructor as
IVueClass
<any>;
if (
srcClass
.
ivueGlobalStore
) {
seen
.
set
(
source
,
source
);
return
source
;
} let
out
;
if (
deepCloneArgs
?.
has
(
srcClass
)) {
activeDeepCloneArgsMap
.
set
(
srcClass
,
deepCloneArgs
);
try {
out
=
source
.clone(...(
deepCloneArgs
.
get
(
srcClass
) ?? []));
} finally {
activeDeepCloneArgsMap
.
delete
(
srcClass
);
} } else {
out
=
source
.clone();
}
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 5: Common Built-ins (Sorted by Likelihood) --- // instanceof is fast, but order matters slightly for CPU branch prediction if (
source
instanceof
Date
) {
return new
Date
(
source
.
getTime
());
} if (
source
instanceof
Map
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const [
k
,
v
] of
source
) {
out
.set(
deepClone
(
k
,
deepCloneArgs
,
seen
),
deepClone
(
v
,
deepCloneArgs
,
seen
)
); } return
out
;
} if (
source
instanceof
Set
) {
const
out
= new (
source
.
constructor
as any)();
seen
.
set
(
source
,
out
);
for (const
v
of
source
) {
out
.add(
deepClone
(
v
,
deepCloneArgs
,
seen
));
} return
out
;
} // --- TIER 6: Rare / Heavy Objects (The "Slow" Tail) --- if (
source
instanceof
RegExp
) {
const
out
= new
RegExp
(
source
.
source
,
source
.
flags
);
out
.
lastIndex
=
source
.
lastIndex
;
return
out
;
} if (
source
instanceof
Error
) {
const
out
= new (
source
.
constructor
as any)(
source
.
message
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
} if (
source
instanceof
Promise
||
source
instanceof
WeakMap
||
source
instanceof
WeakSet
) {
seen
.
set
(
source
,
source
);
return
source
;
} if (
source
instanceof
DataView
) {
// Clone the underlying buffer to ensure deep copy const
out
= new
DataView
(
source
.
buffer
.
slice
(0),
source
.
byteOffset
,
source
.
byteLength
);
seen
.
set
(
source
,
out
);
return
out
;
} // Binary data (less common in UI state, but supported) if (
ArrayBuffer
.
isView
(
source
) && !(
source
instanceof
DataView
)) {
const
out
= new (
source
.
constructor
as any)(
source
);
seen
.
set
(
source
,
out
);
return
out
;
} if (
source
instanceof
ArrayBuffer
) {
const
out
=
source
.
slice
(0);
seen
.
set
(
source
,
out
);
return
out
;
} // --- TIER 7: Final Fallback (Custom User Classes) --- // If it's a class instance not handled above const
out
=
createObject
(
prototype
);
seen
.
set
(
source
,
out
);
copyOwnProps
(
source
,
out
,
deepCloneArgs
,
seen
);
return
out
;
}; /** * Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps. * Useful for SSR scenarios where you want to reset the cache between requests. * Also useful for testing purposes to ensure a clean state. */ export const
clearCache
= () => {
accessorsMethodsMaps
= new
WeakMap
();
propertiesAccessorsMaps
= new
WeakMap
();
activeDeepCloneArgsMap
= new
WeakMap
();
}; /** Exported for testing purposes. */ export const
__test__
= {
copyOwnProps
,
getClassAccessorsMethodsMap
,
getClassPropertiesAccessorsMap
,
}; /** Necessary ivue.ts to be treated as a module. */ export {};

Use ref() for properties

ivue recommends all class properties to be defined as ref() to be able to interoperate with defineExpose(), if you simply pass reactive props which are not Refs through defineExpose(), they will lose reactivity. ref() refs just like computed refs get flattened into the reactive() object, so there is no need to worry about using .value. The ref() refs are necessary just internally for Vue 3 to know which refs to keep reactive.

Unwrap (de-Ref) the types

Next, we convert the types back to their normal types as if they have no reactivity at all, so Ref<number> is number in ivue, so rather than going in the direction of complexifying the types, we are going in the opposite direction towards simplification.

Use standard declaration syntax for functions

ivue recommends all class functions to be defined in plain full function style (not arrow functions), this allows all ivue classes to be extensible at any point. By using plain standard functions, it allows the developer to be able to override them at any time by simply extending the class.

Do not use arrow functions in class declarations

WARNING

Arrow functions break full extensibility of classes because they carry their own context at the point of declaration, so avoid using them inside of ivue classes.

constructor() vs .init()

Use constructor() to assign properties of the class and cast Refs to Unwrapped bare types.

Use .init() to declare reactive state functions like watch, watchEffect, and lifecycle hooks like onMounted, onBeforeMount etc, do assignments of reactive properties, since .init() already has access to reactive() state through this.


Inside the constructor() method you still have access to non-reactive state, because when constructor() is initialized, it does NOT yet have access to the reactive properties of the class, since it was not yet converted to reactive() by ivue, so if you use the properties like Refs or ComputedRefs inside constructor() you would have to use them with the .value.

As a general rule, there is no need to manipulate the values in the constructor(), use constructor only for assigning the properties and casting the types of those assigned properties to the unwrapped (de-Refed) final state of the resulting reactive() object.

Let's look at a constructor() vs .init() example:

vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { ivue, iref, iuse } from 'ivue';

class Counter {
  constructor(public span?: HTMLElement) {
    // ❌ Do not do this in the constructor() because this.span 
    // still refers to this.span.value here:
    // onMounted(() => { 
    //   (this.span as HTMLElement /*❌*/).innerHTML = 'Initial span text!';
    // });
  }
  init() {
    // ✅ Do this inside init() because this.span (.value) is now
    // flattened & (this) is now reactive():
    onMounted(() => {
      (this.span as HTMLElement/*✅*/).innerHTML = 'Initial span text!';
    });
  }
  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>
  <a href="javascript:void(0)" @click="() => counter.increment()">Increment</a><br />
  Count: {{ counter.count }} <br />
  <span ref="span"></span>
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterExternalRefsDetailed />
</template>
Result
Increment
Count: 0

Unwrapping Refs

The key to working with ivue is understanding correctly the way Vue 3 does automatic Unwrapping of Refs when they are passed into the reactive() object. In that regard we are not relying on some magic ivue behavior but rather the default behavior of reactive() Vue 3 function.

To match that unwrapping behavior, our class needs to Unwrap (or de-Ref) the types of Composables, Refs, ComputedRefs, if they are being passed into the constructor, to get their raw basic types those Refs are pointing to, so that you can start operating in the ivue environment, where there is no need to worry about .value. This unwrapping should be mainly done inside the constructor() method.

Naming Conventions

To benefit from the full power of ivue, it is recommended to extract the classes into separate files. What has been an effective pattern is to name the classes and put them right beside components in the same folder that these classes are being used with. So if you have CounterComponent.vue component, it can have a class inside CounterComponentClass.ts, and you can store props, emits, and other runtime definitions inside CounterComponentProps.ts.





File Uploader