0
# Advanced Features
1
2
Mixins, custom decorators, TypeScript JSX support, and utility functions for advanced Vue component composition.
3
4
## Capabilities
5
6
### Mixins Function
7
8
Function for creating mixins with multiple Vue component classes, supporting full ES class inheritance.
9
10
```typescript { .api }
11
/**
12
* Creates a mixin class from multiple Vue component classes
13
* @param conses - Array of component constructor classes to mix
14
* @returns Mixed class with combined functionality
15
*/
16
function mixins<T extends VueCons[]>(...conses: T): MixedClass<T>;
17
18
type MixedClass<Mixins extends VueCons[], Base extends VueCons = VueCons> =
19
Mixins extends [infer T extends VueCons, ...infer E extends VueCons[]] ?
20
MixedClass<E, VueCons<InstanceType<Base> & InstanceType<T>, MergeIdentityType<InstanceType<T>[typeof IdentitySymbol], InstanceType<Base>[typeof IdentitySymbol]>>> :
21
Base;
22
```
23
24
**Usage Examples:**
25
26
```typescript
27
import { Component, mixins, Setup, Prop } from "vue-facing-decorator";
28
import { ref } from "vue";
29
30
// Define base mixins
31
@Component
32
class LoggingMixin {
33
log(message: string) {
34
console.log(`[${this.constructor.name}] ${message}`);
35
}
36
37
created() {
38
this.log("Component created");
39
}
40
}
41
42
@Component
43
class CounterMixin {
44
@Setup(() => ref(0))
45
count!: number;
46
47
increment() {
48
this.count++;
49
this.log?.(`Count incremented to ${this.count}`);
50
}
51
52
decrement() {
53
this.count--;
54
this.log?.(`Count decremented to ${this.count}`);
55
}
56
}
57
58
@Component
59
class TimestampMixin {
60
@Setup(() => ref(Date.now()))
61
createdAt!: number;
62
63
getAge() {
64
return Date.now() - this.createdAt;
65
}
66
}
67
68
// Combine mixins
69
@Component
70
class MixedComponent extends mixins(LoggingMixin, CounterMixin, TimestampMixin) {
71
@Prop({ type: String, required: true })
72
title!: string;
73
74
mounted() {
75
this.log(`Mounted with title: ${this.title}`);
76
this.log(`Component age: ${this.getAge()}ms`);
77
}
78
79
handleClick() {
80
this.increment();
81
this.log(`Click handled, age: ${this.getAge()}ms`);
82
}
83
}
84
85
// Multiple inheritance chains
86
@Component
87
class BaseFeature {
88
baseMethod() {
89
return "from base";
90
}
91
}
92
93
@Component
94
class FeatureA extends BaseFeature {
95
featureAMethod() {
96
return "from feature A";
97
}
98
}
99
100
@Component
101
class FeatureB extends BaseFeature {
102
featureBMethod() {
103
return "from feature B";
104
}
105
}
106
107
@Component
108
class CombinedFeatures extends mixins(FeatureA, FeatureB) {
109
combinedMethod() {
110
return `${this.baseMethod()}, ${this.featureAMethod()}, ${this.featureBMethod()}`;
111
}
112
}
113
```
114
115
### Custom Decorator Factory
116
117
Factory function for creating custom decorators with full integration into the Vue component system.
118
119
```typescript { .api }
120
/**
121
* Factory for creating custom decorators
122
* @param creator - Function that modifies the Vue component option
123
* @param options - Optional configuration for the decorator
124
* @returns Custom decorator function
125
*/
126
function createDecorator(
127
creator: Creator,
128
options?: { preserve?: boolean }
129
): PropertyDecorator;
130
131
type Creator = (options: any, key: string) => void;
132
133
interface CustomDecoratorRecord {
134
key: string;
135
creator: Creator;
136
preserve: boolean;
137
}
138
```
139
140
**Usage Examples:**
141
142
```typescript
143
import { Component, createDecorator, Setup } from "vue-facing-decorator";
144
import { ref } from "vue";
145
146
// Create custom validation decorator
147
const Validate = createDecorator((option: any, key: string) => {
148
// Add validation logic to component options
149
option.methods = option.methods || {};
150
option.methods[`validate${key.charAt(0).toUpperCase() + key.slice(1)}`] = function() {
151
// Custom validation logic
152
return this[key] !== null && this[key] !== undefined;
153
};
154
});
155
156
// Create custom logging decorator
157
const AutoLog = createDecorator((option: any, key: string) => {
158
option.watch = option.watch || {};
159
option.watch[key] = {
160
handler(newVal: any, oldVal: any) {
161
console.log(`${key} changed from`, oldVal, 'to', newVal);
162
},
163
immediate: false
164
};
165
});
166
167
// Create debounced decorator
168
const Debounced = (delay: number) => createDecorator((option: any, key: string) => {
169
const originalMethod = option.methods?.[key];
170
if (originalMethod) {
171
let timeoutId: any;
172
option.methods[key] = function(...args: any[]) {
173
clearTimeout(timeoutId);
174
timeoutId = setTimeout(() => {
175
originalMethod.apply(this, args);
176
}, delay);
177
};
178
}
179
});
180
181
// Usage of custom decorators
182
@Component
183
class CustomDecoratorComponent {
184
@Validate()
185
@AutoLog()
186
@Setup(() => ref(""))
187
username!: string;
188
189
@AutoLog()
190
@Setup(() => ref(0))
191
score!: number;
192
193
@Debounced(300)
194
search() {
195
console.log("Searching for:", this.username);
196
}
197
198
mounted() {
199
// Validation method was auto-generated
200
console.log("Username valid:", this.validateUsername());
201
}
202
}
203
204
// Persistent decorator that preserves original behavior
205
const Trackable = createDecorator((option: any, key: string) => {
206
// Track property access
207
option.created = option.created || [];
208
if (!Array.isArray(option.created)) {
209
option.created = [option.created];
210
}
211
212
option.created.push(function() {
213
console.log(`Trackable property "${key}" initialized`);
214
});
215
}, { preserve: true });
216
217
@Component
218
class TrackableComponent {
219
@Trackable()
220
@Setup(() => ref("tracked value"))
221
trackedProp!: string;
222
}
223
```
224
225
### TypeScript JSX Support
226
227
Generic function providing TypeScript support for JSX/TSX components with full type inference.
228
229
```typescript { .api }
230
/**
231
* Generic function for TypeScript JSX/TSX type support
232
* @returns Type-enhanced component decorator
233
*/
234
function TSX<
235
Properties extends IdentityType['props'] = {},
236
Events extends IdentityType['events'] = {},
237
IT extends IdentityType = { props: Properties; events: Events }
238
>(): <C extends VueCons>(cons: C) => VueCons<InstanceType<C>, MergeIdentityType<IT, InstanceType<C>[typeof IdentitySymbol]>>;
239
240
interface IdentityType {
241
props: Record<string, any>;
242
events: Record<string, any>;
243
}
244
```
245
246
**Usage Examples:**
247
248
```typescript
249
import { Component, TSX, Prop, Emit } from "vue-facing-decorator";
250
import { h, VNode } from "vue";
251
252
// Define types for props and events
253
interface ButtonProps {
254
variant: 'primary' | 'secondary';
255
disabled?: boolean;
256
size?: 'small' | 'medium' | 'large';
257
}
258
259
interface ButtonEvents {
260
click: (event: MouseEvent) => void;
261
focus: () => void;
262
blur: () => void;
263
}
264
265
// Apply TSX typing
266
@TSX<ButtonProps, ButtonEvents>()
267
@Component
268
class TypedButton {
269
@Prop({ type: String, required: true, validator: (v: string) => ['primary', 'secondary'].includes(v) })
270
variant!: 'primary' | 'secondary';
271
272
@Prop({ type: Boolean, default: false })
273
disabled!: boolean;
274
275
@Prop({ type: String, default: 'medium', validator: (v: string) => ['small', 'medium', 'large'].includes(v) })
276
size!: 'small' | 'medium' | 'large';
277
278
@Emit('click')
279
handleClick(event: MouseEvent) {
280
if (!this.disabled) {
281
return event;
282
}
283
}
284
285
@Emit('focus')
286
handleFocus() {
287
console.log('Button focused');
288
}
289
290
@Emit('blur')
291
handleBlur() {
292
console.log('Button blurred');
293
}
294
295
render(): VNode {
296
return h('button', {
297
class: [
298
'btn',
299
`btn--${this.variant}`,
300
`btn--${this.size}`,
301
{ 'btn--disabled': this.disabled }
302
],
303
disabled: this.disabled,
304
onClick: this.handleClick,
305
onFocus: this.handleFocus,
306
onBlur: this.handleBlur
307
}, this.$slots.default?.());
308
}
309
}
310
311
// Complex component with nested types
312
interface FormData {
313
username: string;
314
email: string;
315
age: number;
316
}
317
318
interface FormProps {
319
initialData?: Partial<FormData>;
320
readonly?: boolean;
321
}
322
323
interface FormEvents {
324
submit: (data: FormData) => void;
325
change: (field: keyof FormData, value: any) => void;
326
reset: () => void;
327
}
328
329
@TSX<FormProps, FormEvents>()
330
@Component
331
class TypedForm {
332
@Prop({ type: Object, default: () => ({}) })
333
initialData!: Partial<FormData>;
334
335
@Prop({ type: Boolean, default: false })
336
readonly!: boolean;
337
338
private formData: FormData = {
339
username: '',
340
email: '',
341
age: 0,
342
...this.initialData
343
};
344
345
@Emit('submit')
346
handleSubmit() {
347
return { ...this.formData };
348
}
349
350
@Emit('change')
351
handleFieldChange(field: keyof FormData, value: any) {
352
this.formData[field] = value as any;
353
return { field, value };
354
}
355
356
@Emit('reset')
357
handleReset() {
358
this.formData = { username: '', email: '', age: 0, ...this.initialData };
359
}
360
361
render(): VNode {
362
return h('form', {
363
onSubmit: (e: Event) => {
364
e.preventDefault();
365
this.handleSubmit();
366
}
367
}, [
368
h('input', {
369
value: this.formData.username,
370
disabled: this.readonly,
371
onInput: (e: Event) => this.handleFieldChange('username', (e.target as HTMLInputElement).value)
372
}),
373
h('input', {
374
type: 'email',
375
value: this.formData.email,
376
disabled: this.readonly,
377
onInput: (e: Event) => this.handleFieldChange('email', (e.target as HTMLInputElement).value)
378
}),
379
h('input', {
380
type: 'number',
381
value: this.formData.age,
382
disabled: this.readonly,
383
onInput: (e: Event) => this.handleFieldChange('age', parseInt((e.target as HTMLInputElement).value))
384
}),
385
h('button', { type: 'submit' }, 'Submit'),
386
h('button', { type: 'button', onClick: this.handleReset }, 'Reset')
387
]);
388
}
389
}
390
```
391
392
## Advanced Type System
393
394
The advanced features leverage a sophisticated type system for full TypeScript integration:
395
396
```typescript { .api }
397
// Core identity system for type preservation
398
interface Identity<IT extends IdentityType = { props: {}, events: {} }> {
399
readonly [IdentitySymbol]: IT;
400
}
401
402
// Type merging utility for mixins
403
type MergeIdentityType<A extends IdentityType, B extends IdentityType> = {
404
props: A['props'] & B['props'];
405
events: A['events'] & B['events'];
406
};
407
408
// Vue constructor with identity preservation
409
type VueCons<
410
RawInstance extends Identity = Identity,
411
IT extends IdentityType = { props: {}, events: {} }
412
> = {
413
new(): ComponentPublicInstance<IT['props'] & EventHandlers<IT['events']>> &
414
Identity<IT> &
415
Omit<RawInstance, typeof IdentitySymbol>;
416
};
417
418
// Event handler type generation
419
type EventHandlers<Events extends Record<string, any>> = {
420
[K in keyof Events as `on${Capitalize<K & string>}`]?:
421
Events[K] extends Function ? Events[K] : (param: Events[K]) => any;
422
};
423
```
424
425
**Complete Advanced Example:**
426
427
```typescript
428
import { Component, mixins, createDecorator, TSX, Setup, Prop, Emit } from "vue-facing-decorator";
429
import { ref, computed } from "vue";
430
431
// Custom decorator for automatic validation
432
const AutoValidate = createDecorator((option: any, key: string) => {
433
option.computed = option.computed || {};
434
option.computed[`${key}Valid`] = function() {
435
const value = this[key];
436
return value !== null && value !== undefined && value !== '';
437
};
438
});
439
440
// Logging mixin
441
@Component
442
class LoggingMixin {
443
log(level: 'info' | 'warn' | 'error', message: string) {
444
console[level](`[${this.constructor.name}] ${message}`);
445
}
446
}
447
448
// Validation mixin
449
@Component
450
class ValidationMixin {
451
@Setup(() => ref<string[]>([]))
452
errors!: string[];
453
454
addError(field: string, message: string) {
455
this.errors.push(`${field}: ${message}`);
456
}
457
458
clearErrors() {
459
this.errors.splice(0);
460
}
461
462
get isValid() {
463
return this.errors.length === 0;
464
}
465
}
466
467
// Complex component with all advanced features
468
interface ContactFormProps {
469
mode: 'create' | 'edit';
470
initialData?: { name: string; email: string; phone: string };
471
}
472
473
interface ContactFormEvents {
474
save: (data: { name: string; email: string; phone: string }) => void;
475
cancel: () => void;
476
validate: (isValid: boolean) => void;
477
}
478
479
@TSX<ContactFormProps, ContactFormEvents>()
480
@Component
481
class AdvancedContactForm extends mixins(LoggingMixin, ValidationMixin) {
482
@Prop({ type: String, required: true })
483
mode!: 'create' | 'edit';
484
485
@Prop({ type: Object, default: () => ({ name: '', email: '', phone: '' }) })
486
initialData!: { name: string; email: string; phone: string };
487
488
@AutoValidate()
489
@Setup(() => ref(''))
490
name!: string;
491
492
@AutoValidate()
493
@Setup(() => ref(''))
494
email!: string;
495
496
@AutoValidate()
497
@Setup(() => ref(''))
498
phone!: string;
499
500
@Setup(() => computed(() => this.nameValid && this.emailValid && this.phoneValid && this.isValid))
501
formValid!: boolean;
502
503
created() {
504
this.log('info', `Form created in ${this.mode} mode`);
505
Object.assign(this, this.initialData);
506
}
507
508
@Emit('save')
509
handleSave() {
510
this.clearErrors();
511
this.validateForm();
512
513
if (this.formValid) {
514
this.log('info', 'Form saved successfully');
515
return { name: this.name, email: this.email, phone: this.phone };
516
} else {
517
this.log('warn', 'Form validation failed');
518
return null;
519
}
520
}
521
522
@Emit('cancel')
523
handleCancel() {
524
this.log('info', 'Form cancelled');
525
}
526
527
@Emit('validate')
528
validateForm() {
529
this.clearErrors();
530
531
if (!this.name) this.addError('name', 'Name is required');
532
if (!this.email) this.addError('email', 'Email is required');
533
if (!this.phone) this.addError('phone', 'Phone is required');
534
535
return this.formValid;
536
}
537
538
render() {
539
return h('form', { class: 'contact-form' }, [
540
h('h2', this.mode === 'create' ? 'Create Contact' : 'Edit Contact'),
541
h('input', {
542
placeholder: 'Name',
543
value: this.name,
544
onInput: (e: Event) => this.name = (e.target as HTMLInputElement).value
545
}),
546
h('input', {
547
type: 'email',
548
placeholder: 'Email',
549
value: this.email,
550
onInput: (e: Event) => this.email = (e.target as HTMLInputElement).value
551
}),
552
h('input', {
553
type: 'tel',
554
placeholder: 'Phone',
555
value: this.phone,
556
onInput: (e: Event) => this.phone = (e.target as HTMLInputElement).value
557
}),
558
this.errors.length > 0 && h('ul', { class: 'errors' },
559
this.errors.map(error => h('li', error))
560
),
561
h('div', { class: 'buttons' }, [
562
h('button', { type: 'button', onClick: this.handleSave }, 'Save'),
563
h('button', { type: 'button', onClick: this.handleCancel }, 'Cancel')
564
])
565
]);
566
}
567
}
568
```