ycs77-vue

📁 ycs77/skills 📅 1 day ago
0
总安装量
2
周安装量
安装命令
npx skills add https://github.com/ycs77/skills --skill ycs77-vue

Agent 安装分布

claude-code 2

Skill 文档

Lucas Yang’s Vue Conventions

Opinionated Vue 3 and TypeScript patterns emphasizing minimal boilerplate, readability, and practical simplicity for real-world projects.

TypeScript Formatting

Standard: 2 spaces, single quotes, no semicolons, trailing commas.

Vue SFC Patterns

1. SFC Block Order

Always place <template> before <script setup lang="ts"> in Single File Components. This follows the natural reading flow from structure (what to render) to behavior (how it works).

Good:

<template>
  <div class="container">
    <h1>{{ title }}</h1>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const title = ref('Hello World')
</script>

Avoid:

<script setup lang="ts">
import { ref } from 'vue'

const title = ref('Hello World')
</script>

<template>
  <div class="container">
    <h1>{{ title }}</h1>
  </div>
</template>

2. Inline Props/Emits Types

Prefer inline type declarations for defineProps and defineEmits. This approach provides better readability and reduces the need for additional type declarations, making it easier to understand the component’s API at a glance.

Good:

<script setup lang="ts">
const props = defineProps<{
  title: string
  count?: number
  items: {
    id: string
    name: string
  }[]
}>()

const emit = defineEmits<{
  update: [value: string]
  delete: [id: string]
}>()
</script>

Avoid:

<script setup lang="ts">
interface Props {
  title: string
  count?: number
  items: {
    id: string
    name: string
  }[]
}

interface Emits {
  update: [value: string]
  delete: [id: string]
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

Exception: Only extract to a separate interface when the type is reused across multiple components or needs to be exported.

3. Use reactive() for Form State

When managing form state, prefer using reactive() to create a single reactive object that holds all form fields. This approach simplifies state management and reduces boilerplate code compared to using multiple ref() calls for each field.

Good:

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.email" type="email">
    <input v-model="form.password" type="password">
    <input v-model="form.rememberMe" type="checkbox">
  </form>
</template>

<script setup lang="ts">
import { reactive } from 'vue'

const form = reactive({
  email: '',
  password: '',
  rememberMe: false,
})

function handleSubmit() {
  console.log(form) // Clean object, easy to submit
}

function resetForm() {
  form.email = ''
  form.password = ''
  form.rememberMe = false
}
</script>

Avoid:

<script setup lang="ts">
import { ref } from 'vue'

const email = ref('')
const password = ref('')
const rememberMe = ref(false)

function handleSubmit() {
  console.log({
    email: email.value,
    password: password.value,
    rememberMe: rememberMe.value,
  })
}
</script>

Rationale: Grouping related state reduces .value boilerplate and makes form submission cleaner. Use individual ref() only for truly independent state.

4. Use Type Assertion for ref() with Complex Types

When using ref() with interface or complex object types, use type assertion with as Ref<Type> to avoid TypeScript errors. For primitive types (string, number, boolean) or enum types, you can safely use the generic parameter ref<Type>().

Good:

<script setup lang="ts">
import type { Ref } from 'vue'
import type { User } from '@/types'
import { ref } from 'vue'

// Complex type - use type assertion
const user = ref() as Ref<User>
const users = ref([]) as Ref<User[]>
const isSelectedUser = ref(null) as Ref<User | null>  // Prefer null over undefined

// Primitive types - generic parameter is fine
const count = ref<number>(0)
const isActive = ref<boolean>(false)
const status = ref<Status>(Status.Pending)
</script>

Avoid:

<script setup lang="ts">
import type { User } from '@/types'
import { ref } from 'vue'

// May cause: Type '...' is not assignable to type 'Ref<User>'
const user = ref<User>()
</script>

Rationale: Vue’s official documentation recommends ref<Type>(), but this project prefers type assertion as Ref<Type> for complex types to avoid type conflicts while maintaining type safety.

5. Use Same-name Shorthand for Bindings

When binding a prop or attribute where the variable name matches the attribute name, use the same-name shorthand syntax to reduce redundancy. This feature is available in Vue 3.4+ and mirrors JavaScript’s object property shorthand pattern.

Good:

<template>
  <div :id :title>
    <MyComponent :user-name :count :is-active />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const id = ref('app')
const title = ref('Dashboard')
const userName = ref('Lucas')  // Matches :user-name (kebab-case)
const count = ref(42)
const isActive = ref(true)
</script>

Avoid:

<template>
  <div :id="id" :title="title">
    <MyComponent :user-name="userName" :count="count" :is-active="isActive" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const id = ref('app')
const title = ref('Dashboard')
const userName = ref('Lucas')
const count = ref(42)
const isActive = ref(true)
</script>

Rationale: Same-name shorthand reduces visual noise and follows the familiar JavaScript ES6 object property shorthand pattern. It makes templates more concise when variable names already describe the bound attribute clearly. Note that kebab-case attributes (:user-name) automatically match camelCase variables (userName). For props that require props. prefix (see Rule 6), do not use shorthand.

6. Avoid props. Prefix in Templates

Do not use props. prefix when accessing props in <template>. Vue’s <script setup> destructures props into the template scope automatically, so the prefix is unnecessary. Only add props. when the prop name conflicts with a language keyword or HTML attribute (e.g., class, as). In those cases, do not apply same-name shorthand binding — always use the explicit props. form.

Good:

<template>
  <div :class="props.class">
    <h1>{{ title }}</h1>
    <component :is="props.as">content</component>
  </div>
</template>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'

const props = defineProps<{
  title: string
  as: string
  class?: HTMLAttributes['class']
}>()
</script>

Avoid:

<template>
  <div :class>
    <h1>{{ props.title }}</h1>
    <component :is="as">content</component>
  </div>
</template>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'

const props = defineProps<{
  title: string
  as: string
  class?: HTMLAttributes['class']
}>()
</script>

Rationale: Omitting props. keeps templates concise and consistent with how ref() and computed() values are accessed. Language keywords and HTML attributes like class and as require props. to avoid conflicts, and must use explicit binding (:class="props.class") instead of shorthand (:class) to prevent resolving to the wrong scope.