You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.1 KiB
7.1 KiB
Component Communication Guide
Overview
This guide establishes our preferred patterns for component communication in Vue.js applications, with a focus on maintainability, type safety, and developer experience.
Core Principle: Function Props Over $emit
Preference: Use function props for business logic and data operations, reserve $emit for DOM-like events.
Why Function Props?
- Better TypeScript Support: Full type checking of parameters and return values
- Superior IDE Navigation: Ctrl+click takes you directly to implementation
- Explicit Contracts: Clear declaration of what functions a component needs
- Easier Testing: Simple to mock and test in isolation
- Flexibility: Can pass any function, not just event handlers
When to Use $emit
- DOM-like Events:
@click
,@input
,@submit
,@change
- Lifecycle Events:
@mounted
,@before-unmount
,@updated
- Form Validation:
@validation-error
,@validation-success
- Event Bubbling: When events need to bubble through multiple components
- Vue DevTools Integration: When you want events visible in DevTools timeline
Implementation Patterns
Function Props Pattern
// Child Component
@Component({
name: "MyComponent"
})
export default class MyComponent extends Vue {
@Prop({ required: true }) onSave!: (data: SaveData) => Promise<void>;
@Prop({ required: true }) onCancel!: () => void;
@Prop({ required: false }) onValidate?: (data: FormData) => boolean;
async handleSave() {
const data = this.collectFormData();
await this.onSave(data);
}
handleCancel() {
this.onCancel();
}
}
<!-- Parent Template -->
<MyComponent
:on-save="handleSave"
:on-cancel="handleCancel"
:on-validate="validateForm"
/>
$emit Pattern (for DOM-like events)
// Child Component
@Component({
name: "FormComponent"
})
export default class FormComponent extends Vue {
@Emit("submit")
handleSubmit() {
return this.formData;
}
@Emit("input")
handleInput(value: string) {
return value;
}
}
<!-- Parent Template -->
<FormComponent
@submit="handleFormSubmit"
@input="handleInputChange"
/>
Automatic Code Generation Guidelines
Component Template Generation
When generating component templates, follow these patterns:
Function Props Template
<template>
<div class="component-name">
<!-- Component content -->
<button @click="handleAction">
{{ buttonText }}
</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
@Component({
name: "ComponentName"
})
export default class ComponentName extends Vue {
@Prop({ required: true }) onAction!: () => void;
@Prop({ required: true }) buttonText!: string;
@Prop({ required: false }) disabled?: boolean;
handleAction() {
if (!this.disabled) {
this.onAction();
}
}
}
</script>
$emit Template (for DOM events)
<template>
<div class="component-name">
<!-- Component content -->
<button @click="handleClick">
{{ buttonText }}
</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
@Component({
name: "ComponentName"
})
export default class ComponentName extends Vue {
@Prop({ required: true }) buttonText!: string;
@Prop({ required: false }) disabled?: boolean;
@Emit("click")
handleClick() {
return { disabled: this.disabled };
}
}
</script>
Code Generation Rules
1. Function Props for Business Logic
- Data operations: Save, delete, update, validate
- Navigation: Route changes, modal opening/closing
- State management: Store actions, state updates
- API calls: Data fetching, form submissions
2. $emit for User Interactions
- Click events: Button clicks, link navigation
- Form events: Input changes, form submissions
- Lifecycle events: Component mounting, unmounting
- UI events: Focus, blur, scroll, resize
3. Naming Conventions
Function Props:
// Action-oriented names
onSave: (data: SaveData) => Promise<void>
onDelete: (id: string) => Promise<void>
onUpdate: (item: Item) => void
onValidate: (data: FormData) => boolean
onNavigate: (route: string) => void
$emit Events:
// Event-oriented names
@click: (event: MouseEvent) => void
@input: (value: string) => void
@submit: (data: FormData) => void
@focus: (event: FocusEvent) => void
@mounted: () => void
TypeScript Integration
Function Prop Types
// Define reusable function types
interface SaveHandler {
(data: SaveData): Promise<void>;
}
interface ValidationHandler {
(data: FormData): boolean;
}
// Use in components
@Prop({ required: true }) onSave!: SaveHandler;
@Prop({ required: true }) onValidate!: ValidationHandler;
Event Types
// Define event payload types
interface ClickEvent {
target: HTMLElement;
timestamp: number;
}
@Emit("click")
handleClick(): ClickEvent {
return {
target: this.$el,
timestamp: Date.now()
};
}
Testing Guidelines
Function Props Testing
// Easy to mock and test
const mockOnSave = jest.fn();
const wrapper = mount(MyComponent, {
propsData: {
onSave: mockOnSave
}
});
await wrapper.vm.handleSave();
expect(mockOnSave).toHaveBeenCalledWith(expectedData);
$emit Testing
// Requires event simulation
const wrapper = mount(MyComponent);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('click')).toBeTruthy();
Migration Strategy
From $emit to Function Props
- Identify business logic events (not DOM events)
- Add function props to component interface
- Update parent components to pass functions
- Remove $emit decorators and event handlers
- Update tests to use function mocks
Example Migration
Before ($emit):
@Emit("save")
handleSave() {
return this.formData;
}
After (Function Props):
@Prop({ required: true }) onSave!: (data: FormData) => void;
handleSave() {
this.onSave(this.formData);
}
Best Practices Summary
- Use function props for business logic, data operations, and complex interactions
- Use $emit for DOM-like events, lifecycle events, and simple user interactions
- Be consistent within your codebase
- Document your patterns for team alignment
- Consider TypeScript when choosing between approaches
- Test both patterns appropriately
Code Generation Templates
Component Generator Input
interface ComponentSpec {
name: string;
props: Array<{
name: string;
type: string;
required: boolean;
isFunction: boolean;
}>;
events: Array<{
name: string;
payloadType?: string;
}>;
template: string;
}
Generated Output
// Generator should automatically choose function props vs $emit
// based on the nature of the interaction (business logic vs DOM event)
This guide ensures consistent, maintainable component communication patterns across the application.