关于 ycs77-vue
ycs77-vue 是面向 Vue 3 与 TypeScript 项目的编码规范技能,适合希望保持代码简洁、易读的开发者。它涵盖单文件组件结构、类型声明和响应式状态等约定。
主要功能
- 01TypeScript 格式约定
- 02Vue 单文件组件区块顺序
- 03内联 props 与 emits 类型
- 04使用 reactive() 管理表单状态
- 05复杂类型的 ref() 写法
- 06模板中的 props 访问规则
适用场景
- 01统一 Vue 3 项目的代码风格
- 02编写组件时组织 props 与 emits 类型
- 03管理表单及复杂响应式状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ycs77/ skills --skill ycs77-vue安装后在 Claude Code 中输入/ycs77-vue确认已加载。
详细介绍
Lucas Yang 的 Vue 约定
面向真实项目的 Vue 3 和 TypeScript 实践,注重减少样板代码、提高可读性和实用简洁性。
TypeScript 格式
标准:2 个空格、单引号、不加分号、保留尾随逗号。
Vue SFC 模式
1. SFC 块顺序
在单文件组件中,始终将 <template> 放在 <script setup lang="ts"> 之前。这符合自然的阅读顺序:先看结构(要渲染什么),再看行为(如何运作)。
良好示例:
<template>
<div class="container">
<h1>{{ title }}</h1>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const title = ref('Hello World')
</script>
2. 内联 Props/Emits 类型
优先在 defineProps 和 defineEmits 中使用内联类型,以便在调用处查看组件 API。
良好示例:
<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>
例外情况:只有当类型在多个组件中重复使用或需要导出时,才将其提取为单独的接口。
3. 使用 reactive() 管理表单状态
管理表单状态时,优先使用 reactive() 创建一个包含所有表单字段的响应式对象。与为每个字段分别调用多个 ref() 相比,这种方式能简化状态管理并减少样板代码。
良好示例:
<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 or reactive object, easy to submit
}
function resetForm() {
form.email = ''
form.password = ''
form.rememberMe = false
}
</script>
原因:将相关状态分组可减少 .value 样板代码,并使表单提交更简洁。只有对于真正独立的状态,才使用单独的 ref()。
4. 对复杂类型的 ref() 使用类型断言
对于接口或复杂对象类型,优先使用 ref(value) as Ref<Type>,而非 ref<Type>(value),以避免 Vue 深层解包导致的类型冲突。请确保该值符合断言的类型。对于基本类型和枚举类型,使用 ref<Type>()。
表示对象不存在时,优先使用 null 而非 undefined。
良好示例:
<script setup lang="ts">
import type { Ref } from 'vue'
import { ref } from 'vue'
interface User {
id: number
name: string
}
enum Status {
Pending = 'pending',
}
// Complex type - use type assertion
const user = ref({ id: 1, name: 'Lucas' }) as Ref<User>
const users = ref([]) as Ref<User[]>
const isSelectedUser = ref(null) as Ref<User | null>
// Primitive types - generic parameter is fine
const count = ref<number>(0)
const isActive = ref<boolean>(false)
const status = ref<Status>(Status.Pending)
</script>
避免:
<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>
5. 绑定使用同名简写
在 Vue 3.4+ 中,为名称匹配的绑定使用同名简写。
良好示例:
<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')
const count = ref(42)
const isActive = ref(true)
</script>
原因::user-name 与 userName 匹配(从 kebab-case 转为 camelCase)。如果绑定需要显式使用 props. 访问(第 6 节),则使用完整形式。
6. 在模板中直接访问 Props
通过 defineProps 声明的 Props 可在 <template> 中按名称访问。在不产生歧义时,优先使用 {{ title }} 而非 {{ props.title }}。当局部绑定遮蔽了某个 prop 时,使用 props.name。对于 class(JavaScript 保留字)和 as(用于 TypeScript 类型断言)等语法敏感的名称,请显式访问 Props:props.class 和 props.as。不能将单独的 class 用作 :class 简写的表达式;:is="as" 则不会明确指出 as 的来源。
良好示例:
<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>
避免:
<div :class>
<component :is="as">content</component>
</div>