Files
gh-alexanderop-claude-skill…/skills/vue-development/references/component-patterns.md
2025-11-29 17:52:04 +08:00

205 lines
5.1 KiB
Markdown

# Vue Component Patterns Reference
## Table of Contents
- [Props Definition](#props-definition)
- [Emits Definition](#emits-definition)
- [V-Model Bindings](#v-model-bindings)
- [Template Patterns](#template-patterns)
- [Component Naming](#component-naming)
- [V-Model Modifiers](#v-model-modifiers)
## Props Definition
```vue
<script setup lang="ts">
// ✅ CORRECT: TypeScript generics, no const (props not used in script)
defineProps<{
userId: number
userName: string
isActive?: boolean
}>()
// ✅ CORRECT: With const ONLY if props used in script
const props = defineProps<{
count: number
}>()
console.log(props.count) // Used in script
// ✅ CORRECT: With defaults (requires destructuring)
const { theme = 'light', size = 'medium' } = defineProps<{
theme?: string
size?: string
}>()
// ❌ WRONG: Runtime validation when TypeScript available
const props = defineProps({
user: {
type: Object as PropType<User>,
required: true
}
})
// ❌ WRONG: const when props not used
const props = defineProps<{ count: number }>()
// props never referenced in script
</script>
```
**Rules:**
- ALWAYS use TypeScript generics: `defineProps<{ }>()`
- Use `const props =` ONLY if props referenced in script block
- NEVER use runtime validation (`type`, `required`, `PropType`) when TypeScript available
- Destructure ONLY for default values
## Emits Definition
```vue
<script setup lang="ts">
// ✅ CORRECT: TypeScript with event payloads
const emit = defineEmits<{
submit: [formData: FormData]
cancel: []
update: [userId: number, changes: Partial<User>]
}>()
emit('submit', formData)
emit('cancel')
emit('update', 123, { name: 'Updated' })
// ❌ WRONG: Array syntax without types
const emit = defineEmits(['submit', 'cancel'])
// ❌ WRONG: Runtime validation
const emit = defineEmits({
submit: (payload: FormData) => true
})
</script>
```
**Rules:**
- ALWAYS use `const emit = defineEmits<{ }>()`
- ALWAYS define event payload types: `eventName: [arg1: type, arg2: type]` or `eventName: []`
- ALWAYS use camelCase in script, kebab-case in templates
- Use semantic event names: `submit`, `delete`, `userUpdated` (not generic `update`)
## V-Model Bindings
```vue
<script setup lang="ts">
// ✅ CORRECT: Simple v-model
const title = defineModel<string>({ required: true })
// ✅ CORRECT: With options
const [count, modifiers] = defineModel<number>({
default: 0,
get: (value) => Math.max(0, value), // transform on read
set: (value) => {
if (modifiers.even) {
return Math.round(value / 2) * 2
}
return value
}
})
// ✅ CORRECT: Multiple v-models
const firstName = defineModel<string>('firstName')
const age = defineModel<number>('age')
// Usage: <UserForm v-model:first-name="user.firstName" v-model:age="user.age" />
// ❌ WRONG: Manual modelValue implementation
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
// Should use: const modelValue = defineModel<string>()
</script>
```
**Rules:**
- ALWAYS use `defineModel<type>()` for v-model bindings
- NEVER manually define `modelValue` prop + `update:modelValue` emit
- Use named models for multiple bindings: `defineModel<type>('name')`
- defineModel handles all prop/emit wiring automatically
## Template Patterns
```vue
<template>
<!-- CORRECT: Prop shorthand -->
<UserCard :user-id />
<!-- WRONG: Verbose when value matches prop name -->
<UserCard :user-id="userId" />
<!-- CORRECT: Explicit template tags with shorthand slots -->
<Card>
<template #header>
<h2>Title</h2>
</template>
<template #default>
<p>Content</p>
</template>
</Card>
<!-- WRONG: v-slot longhand -->
<Card>
<template v-slot:header>...</template>
</Card>
<!-- WRONG: Implicit default slot (missing template tag) -->
<Card>
<p>Content</p>
</Card>
</template>
```
**Rules:**
- Use `:propName` when value variable matches prop name (instead of `:propName="propName"`)
- ALWAYS use explicit `<template>` tags for ALL slots
- ALWAYS use shorthand: `#slotName` instead of `v-slot:slotName`
- Use kebab-case for props/events in templates, camelCase in script
## Component Naming
```
✅ CORRECT:
src/components/
UserProfile.vue (PascalCase)
SearchButtonClear.vue (General → Specific)
✅ ALSO CORRECT:
src/components/
user-profile.vue (kebab-case)
search-button-clear.vue
❌ WRONG:
ClearSearchButton.vue (Specific → General)
userProfile.vue (camelCase)
```
**Rules:**
- File names: PascalCase OR kebab-case (be consistent in project)
- Component names in script: ALWAYS PascalCase
- Compose names from general to specific: `SearchButtonClear` not `ClearSearchButton`
## V-Model Modifiers
For custom modifiers, fetch the official docs:
<https://vuejs.org/guide/components/v-model.md#handling-v-model-modifiers>
Basic pattern:
```vue
<script setup lang="ts">
const [modelValue, modifiers] = defineModel<string>({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
```