forked from trent_larson/crowd-funder-for-time-pwa
Create comprehensive development guide establishing our preferred patterns for Vue component communication. Document the preference for function props over $emit for business logic while reserving $emit for DOM-like events. Guide covers: - Function props for business logic, data operations, and complex interactions - $emit for DOM-like events, lifecycle events, and simple user interactions - Implementation patterns with TypeScript examples - Testing strategies for both approaches - Migration strategy from $emit to function props - Naming conventions and best practices Establishes consistent, maintainable component communication patterns across the application with focus on type safety and developer experience.
314 lines
7.1 KiB
Markdown
314 lines
7.1 KiB
Markdown
# 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?
|
|
|
|
1. **Better TypeScript Support**: Full type checking of parameters and return values
|
|
2. **Superior IDE Navigation**: Ctrl+click takes you directly to implementation
|
|
3. **Explicit Contracts**: Clear declaration of what functions a component needs
|
|
4. **Easier Testing**: Simple to mock and test in isolation
|
|
5. **Flexibility**: Can pass any function, not just event handlers
|
|
|
|
### When to Use $emit
|
|
|
|
1. **DOM-like Events**: `@click`, `@input`, `@submit`, `@change`
|
|
2. **Lifecycle Events**: `@mounted`, `@before-unmount`, `@updated`
|
|
3. **Form Validation**: `@validation-error`, `@validation-success`
|
|
4. **Event Bubbling**: When events need to bubble through multiple components
|
|
5. **Vue DevTools Integration**: When you want events visible in DevTools timeline
|
|
|
|
## Implementation Patterns
|
|
|
|
### Function Props Pattern
|
|
|
|
```typescript
|
|
// 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();
|
|
}
|
|
}
|
|
```
|
|
|
|
```vue
|
|
<!-- Parent Template -->
|
|
<MyComponent
|
|
:on-save="handleSave"
|
|
:on-cancel="handleCancel"
|
|
:on-validate="validateForm"
|
|
/>
|
|
```
|
|
|
|
### $emit Pattern (for DOM-like events)
|
|
|
|
```typescript
|
|
// Child Component
|
|
@Component({
|
|
name: "FormComponent"
|
|
})
|
|
export default class FormComponent extends Vue {
|
|
@Emit("submit")
|
|
handleSubmit() {
|
|
return this.formData;
|
|
}
|
|
|
|
@Emit("input")
|
|
handleInput(value: string) {
|
|
return value;
|
|
}
|
|
}
|
|
```
|
|
|
|
```vue
|
|
<!-- Parent Template -->
|
|
<FormComponent
|
|
@submit="handleFormSubmit"
|
|
@input="handleInputChange"
|
|
/>
|
|
```
|
|
|
|
## Automatic Code Generation Guidelines
|
|
|
|
### Component Template Generation
|
|
|
|
When generating component templates, follow these patterns:
|
|
|
|
#### Function Props Template
|
|
```vue
|
|
<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)
|
|
```vue
|
|
<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:**
|
|
```typescript
|
|
// 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:**
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// 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
|
|
|
|
1. **Identify business logic events** (not DOM events)
|
|
2. **Add function props** to component interface
|
|
3. **Update parent components** to pass functions
|
|
4. **Remove $emit decorators** and event handlers
|
|
5. **Update tests** to use function mocks
|
|
|
|
### Example Migration
|
|
|
|
**Before ($emit):**
|
|
```typescript
|
|
@Emit("save")
|
|
handleSave() {
|
|
return this.formData;
|
|
}
|
|
```
|
|
|
|
**After (Function Props):**
|
|
```typescript
|
|
@Prop({ required: true }) onSave!: (data: FormData) => void;
|
|
|
|
handleSave() {
|
|
this.onSave(this.formData);
|
|
}
|
|
```
|
|
|
|
## Best Practices Summary
|
|
|
|
1. **Use function props** for business logic, data operations, and complex interactions
|
|
2. **Use $emit** for DOM-like events, lifecycle events, and simple user interactions
|
|
3. **Be consistent** within your codebase
|
|
4. **Document your patterns** for team alignment
|
|
5. **Consider TypeScript** when choosing between approaches
|
|
6. **Test both patterns** appropriately
|
|
|
|
## Code Generation Templates
|
|
|
|
### Component Generator Input
|
|
```typescript
|
|
interface ComponentSpec {
|
|
name: string;
|
|
props: Array<{
|
|
name: string;
|
|
type: string;
|
|
required: boolean;
|
|
isFunction: boolean;
|
|
}>;
|
|
events: Array<{
|
|
name: string;
|
|
payloadType?: string;
|
|
}>;
|
|
template: string;
|
|
}
|
|
```
|
|
|
|
### Generated Output
|
|
```typescript
|
|
// 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. |