Browse Source
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.web-serve-fix
2 changed files with 317 additions and 3 deletions
@ -0,0 +1,314 @@ |
|||||
|
# 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. |
Loading…
Reference in new issue