Files
ui-ux-pro-max-skill/.qoder/rules/vue.md
2026-01-16 08:05:16 +07:00

58 lines
23 KiB
Markdown

---
trigger: glob
glob: src/*.vue
---
| No | Category | Guideline | Description | Do | Don't | Code Good | Code Bad | Severity | Docs URL |
| --- | ------------- | ------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------- | -------------------------------------------------- | ------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| 1 | Composition | Use Composition API for new projects | Composition API offers better TypeScript support and logic reuse | `<script setup>` for components | Options API for new projects | `<script setup>` | `export default { data() }` | Medium | [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html) |
| 2 | Composition | Use script setup syntax | Cleaner syntax with automatic exports | `<script setup>` with `defineProps` | `setup()` function manually | `<script setup>` | `<script> setup() { return {} }` | Low | [SFC Script Setup](https://vuejs.org/api/sfc-script-setup.html) |
| 3 | Reactivity | Use ref for primitives | ref() for primitive values that need reactivity | `ref()` for strings numbers booleans | `reactive()` for primitives | `const count = ref(0)` | `const count = reactive(0)` | Medium | [Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html) |
| 4 | Reactivity | Use reactive for objects | reactive() for complex objects and arrays | `reactive()` for objects with multiple properties | `ref()` for complex objects | `const state = reactive({ user: null })` | `const state = ref({ user: null })` | Medium | - |
| 5 | Reactivity | Access ref values with .value | Remember .value in script unwrap in template | Use `.value` in script | Forget .value in script | `count.value++` | `count++ (in script)` | High | - |
| 6 | Reactivity | Use computed for derived state | Computed properties cache and update automatically | `computed()` for derived values | Methods for derived values | `const doubled = computed(() => count.value * 2)` | `const doubled = () => count.value * 2` | Medium | [Computed Properties](https://vuejs.org/guide/essentials/computed.html) |
| 7 | Reactivity | Use shallowRef for large objects | Avoid deep reactivity for performance | `shallowRef` for large data structures | `ref` for large nested objects | `const bigData = shallowRef(largeObject)` | `const bigData = ref(largeObject)` | Medium | [shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref) |
| 8 | Watchers | Use watchEffect for simple cases | Auto-tracks dependencies | `watchEffect` for simple reactive effects | `watch` with explicit deps when not needed | `watchEffect(() => console.log(count.value))` | `watch(count, (val) => console.log(val))` | Low | [Watchers](https://vuejs.org/guide/essentials/watchers.html) |
| 9 | Watchers | Use watch for specific sources | Explicit control over what to watch | `watch` with specific refs | `watchEffect` for complex conditional logic | `watch(userId, fetchUser)` | `watchEffect with conditionals` | Medium | - |
| 10 | Watchers | Clean up side effects | Return cleanup function in watchers | Return cleanup in `watchEffect` | Leave subscriptions open | `watchEffect((onCleanup) => { onCleanup(unsub) })` | `watchEffect without cleanup` | High | - |
| 11 | Props | Define props with defineProps | Type-safe prop definitions | `defineProps` with TypeScript | Props without types | `defineProps<{ msg: string }>()` | `defineProps(['msg'])` | Medium | [Typing Component Props](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props) |
| 12 | Props | Use withDefaults for default values | Provide defaults for optional props | `withDefaults` with `defineProps` | Defaults in destructuring | `withDefaults(defineProps<Props>(), { count: 0 })` | `const { count = 0 } = defineProps()` | Medium | - |
| 13 | Props | Avoid mutating props | Props should be read-only | Emit events to parent for changes | Direct prop mutation | `emit('update:modelValue', newVal)` | `props.modelValue = newVal` | High | - |
| 14 | Emits | Define emits with defineEmits | Type-safe event emissions | `defineEmits` with types | Emit without definition | `defineEmits<{ change: [id: number] }>()` | `emit('change', id) without define` | Medium | [Typing Component Emits](https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits) |
| 15 | Emits | Use v-model for two-way binding | Simplified parent-child data flow | v-model with modelValue prop | :value + @input manually | `<Child v-model="value"/>` | `<Child :value="value" @input="value = $event"/>` | Low | [v-model](https://vuejs.org/guide/components/v-model.html) |
| 16 | Lifecycle | Use onMounted for DOM access | DOM is ready in onMounted | onMounted for DOM operations | Access DOM in setup directly | `onMounted(() => el.value.focus())` | `el.value.focus() in setup` | High | [Lifecycle Hooks](https://vuejs.org/api/composition-api-lifecycle.html) |
| 17 | Lifecycle | Clean up in onUnmounted | Remove listeners and subscriptions | onUnmounted for cleanup | Leave listeners attached | `onUnmounted(() => window.removeEventListener())` | No cleanup on unmount | High | - |
| 18 | Lifecycle | Avoid onBeforeMount for data | Use onMounted or setup for data fetching | Fetch in onMounted or setup | Fetch in onBeforeMount | `onMounted(async () => await fetchData())` | `onBeforeMount(async () => await fetchData())` | Low | - |
| 19 | Components | Use single-file components | Keep template script style together | .vue files for components | Separate template/script files | Component.vue with all parts | Component.js + Component.html | Low | - |
| 20 | Components | Use PascalCase for components | Consistent component naming | PascalCase in imports and templates | kebab-case in script | `<MyComponent/>` | `<my-component/>` | Low | [Component Naming](https://vuejs.org/style-guide/rules-strongly-recommended.html) |
| 21 | Components | Prefer composition over mixins | Composables replace mixins | Composables for shared logic | Mixins for code reuse | `const { data } = useApi()` | `mixins: [apiMixin]` | Medium | - |
| 22 | Composables | Name composables with use prefix | Convention for composable functions | useFetch useAuth useForm | getData or fetchApi | `export function useFetch()` | `export function fetchData()` | Medium | [Composables](https://vuejs.org/guide/reusability/composables.html) |
| 23 | Composables | Return refs from composables | Maintain reactivity when destructuring | Return ref values | Return reactive objects that lose reactivity | `return { data: ref(null) }` | `return reactive({ data: null })` | Medium | - |
| 24 | Composables | Accept ref or value params | Use toValue for flexible inputs | toValue() or unref() for params | Only accept ref or only value | `const val = toValue(maybeRef)` | `const val = maybeRef.value` | Low | [toValue](https://vuejs.org/api/reactivity-utilities.html#tovalue) |
| 25 | Templates | Use v-bind shorthand | Cleaner template syntax | :prop instead of v-bind:prop | Full v-bind syntax | `<div :class="cls">` | `<div v-bind:class="cls">` | Low | - |
| 26 | Templates | Use v-on shorthand | Cleaner event binding | @event instead of v-on:event | Full v-on syntax | `<button @click="handler">` | `<button v-on:click="handler">` | Low | - |
| 27 | Templates | Avoid v-if with v-for | v-if has higher priority causes issues | Wrap in template or computed filter | v-if on same element as v-for | `<template v-for><div v-if>` | `<div v-for v-if>` | High | [Avoid v-if with v-for](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for) |
| 28 | Templates | Use key with v-for | Proper list rendering and updates | Unique key for each item | Index as key for dynamic lists | `v-for="item in items" :key="item.id"` | `v-for="(item, i) in items" :key="i"` | High | - |
| 29 | State | Use Pinia for global state | Official state management for Vue 3 | Pinia stores for shared state | Vuex for new projects | `const store = useCounterStore()` | Vuex with mutations | Medium | [Pinia](https://pinia.vuejs.org/) |
| 30 | State | Define stores with defineStore | Composition API style stores | Setup stores with defineStore | Options stores for complex state | `defineStore('counter', () => {})` | `defineStore('counter', { state })` | Low | - |
| 31 | State | Use storeToRefs for destructuring | Maintain reactivity when destructuring | storeToRefs(store) | Direct destructuring | `const { count } = storeToRefs(store)` | `const { count } = store` | High | [Destructuring from a Store](https://pinia.vuejs.org/core-concepts/#destructuring-from-a-store) |
| 32 | Routing | Use useRouter and useRoute | Composition API router access | useRouter() useRoute() in setup | this.$router this.$route | `const router = useRouter()` | `this.$router.push()` | Medium | [Composition API Router](https://router.vuejs.org/guide/advanced/composition-api.html) |
| 33 | Routing | Lazy load route components | Code splitting for routes | () => import() for components | Static imports for all routes | `component: () => import('./Page.vue')` | `component: Page` | Medium | [Lazy Loading](https://router.vuejs.org/guide/advanced/lazy-loading.html) |
| 34 | Routing | Use navigation guards | Protect routes and handle redirects | beforeEach for auth checks | Check auth in each component | `router.beforeEach((to) => {})` | Check auth in onMounted | Medium | - |
| 35 | Performance | Use v-once for static content | Skip re-renders for static elements | v-once on never-changing content | v-once on dynamic content | `<div v-once>{{ staticText }}</div>` | `<div v-once>{{ dynamicText }}</div>` | Low | [v-once](https://vuejs.org/api/built-in-directives.html#v-once) |
| 36 | Performance | Use v-memo for expensive lists | Memoize list items | v-memo with dependency array | Re-render entire list always | `<div v-for v-memo="[item.id]">` | `<div v-for>` without memo | Medium | [v-memo](https://vuejs.org/api/built-in-directives.html#v-memo) |
| 37 | Performance | Use shallowReactive for flat objects | Avoid deep reactivity overhead | shallowReactive for flat state | reactive for simple objects | `shallowReactive({ count: 0 })` | `reactive({ count: 0 })` | Low | - |
| 38 | Performance | Use defineAsyncComponent | Lazy load heavy components | defineAsyncComponent for modals dialogs | Import all components eagerly | `defineAsyncComponent(() => import())` | `import HeavyComponent from` | Medium | [defineAsyncComponent](https://vuejs.org/guide/components/async.html) |
| 39 | TypeScript | Use generic components | Type-safe reusable components | Generic with defineComponent | Any types in components | `<script setup lang="ts" generic="T">` | `<script setup>` without types | Medium | [TypeScript Composition API](https://vuejs.org/guide/typescript/composition-api.html) |
| 40 | TypeScript | Type template refs | Proper typing for DOM refs | ref<HTMLInputElement>(null) | ref(null) without type | `const input = ref<HTMLInputElement>(null)` | `const input = ref(null)` | Medium | - |
| 41 | TypeScript | Use PropType for complex props | Type complex prop types | PropType<User> for object props | Object without type | `type: Object as PropType<User>` | `type: Object` | Medium | - |
| 42 | Testing | Use Vue Test Utils | Official testing library | mount shallowMount for components | Manual DOM testing | `import { mount } from '@vue/test-utils'` | `document.createElement` | Medium | [Vue Test Utils](https://test-utils.vuejs.org/) |
| 43 | Testing | Test component behavior | Focus on inputs and outputs | Test props emit and rendered output | Test internal implementation | `expect(wrapper.text()).toContain()` | `expect(wrapper.vm.internalState)` | Medium | - |
| 44 | Forms | Use v-model modifiers | Built-in input handling | .lazy .number .trim modifiers | Manual input parsing | `<input v-model.number="age">` | `<input v-model="age"> then parse` | Low | [v-model Modifiers](https://vuejs.org/guide/essentials/forms.html#modifiers) |
| 45 | Forms | Use VeeValidate or FormKit | Form validation libraries | VeeValidate for complex forms | Manual validation logic | `useField useForm from vee-validate` | Custom validation in each input | Medium | - |
| 46 | Accessibility | Use semantic elements | Proper HTML elements in templates | button nav main for purpose | div for everything | `<button @click>` | `<div @click>` | High | - |
| 47 | Accessibility | Bind aria attributes dynamically | Keep ARIA in sync with state | ":aria-expanded="isOpen"" | Static ARIA values | ":aria-expanded="menuOpen"" | `aria-expanded="true""` | Medium | - |
| 48 | SSR | Use Nuxt for SSR | Full-featured SSR framework | Nuxt 3 for SSR apps | Manual SSR setup | `npx nuxi init my-app` | Custom SSR configuration | Medium | [Nuxt](https://nuxt.com/) |
| 49 | SSR | Handle hydration mismatches | Client/server content must match | ClientOnly for browser-only content | Different content server/client | `<ClientOnly><BrowserWidget/></ClientOnly>` | `<div>{{ Date.now() }}</div>` | High | - |