
src/
├── assets/ # 静态资源(图片、字体等)
├── components/ # 公共组件(PascalCase命名)
│ ├── Button.vue
│ └── UserCard.vue
├── composables/ # 组合式函数(逻辑复用)
│ └── useFetch.ts
├── router/ # 路由配置
│ └── index.ts
├── store/ # 状态管理(Pinia)
│ └── user.ts
├── utils/ # 工具函数
│ └── format.ts
├── views/ # 页面级组件
│ └── Home.vue
├── App.vue # 根组件
└── main.ts # 入口文件组件文件:PascalCase(如 UserProfile.vue) 文件夹:复数形式(如 components/ 而非 component/) 组合式函数:useXxx 前缀(如 useAuth.ts)
setup 语法糖
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { fetchUser } from '@/api/user';
// 响应式数据
const count = ref(0);
const user = ref<UserType | null>(null);
// 计算属性
const doubleCount = computed(() => count.value * 2);
// 生命周期
onMounted(async () => {
user.value = await fetchUser(1);
});
// 类型定义
interface UserType {
id: number;
name: string;
}
</script>Props/Emits 类型化
const props = defineProps<{
modelValue: string;
disabled?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
(e: 'submit'): void;
}>();语义化标签
<template>
<article class="article-card">
<header class="article-header">
<h2>{{ title }}</h2>
</header>
<main class="article-content">
<p>{{ content }}</p>
</main>
</article>
</template>条件渲染与列表
<template>
<!-- 避免 v-if + v-for 同级 -->
<template v-if="users.length">
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<p v-else>No users found</p>
</template>Scoped CSS
<style scoped>
.article-card {
border: 1px solid #eee;
transition: all 0.3s ease;
}
.article-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>CSS 命名规范(BEM 示例)
.article-card {}
.article-card__header {}
.article-card__header--active {}// composables/useCounter.ts
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => count.value++;
const decrement = () => count.value--;
return { count, increment, decrement };
}1. 父传子:使用 props 父组件通过属性(Props)向子组件传递数据。
父组件 (Parent.vue):
<template>
<div>
<ChildComponent :title="pageTitle" :user="currentUser" />
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import ChildComponent from './ChildComponent.vue'
const pageTitle = ref('欢迎来到我的应用')
const currentUser = reactive({
name: '张三',
id: 123
})
</script>子组件 (ChildComponent.vue):
<template>
<div>
<h2>{{ title }}</h2>
<p>用户: {{ user.name }} (ID: {{ user.id }})</p>
</div>
</template>
<script setup>
// 定义接收的 props
const props = defineProps({
title: {
type: String,
required: true
},
user: {
type: Object,
default: () => ({})
}
})
// 在模板中直接使用 props.title 和 props.user
// 注意:在 <script setup> 中,props 是一个对象,需要通过 props.xxx 访问
</script>2. 子传父:使用 emit 子组件通过触发自定义事件向父组件传递数据。
子组件 (ChildComponent.vue):
<template>
<div>
<button @click="handleClick">点击我通知父组件</button>
</div>
</template>
<script setup>
// 定义组件可以触发的事件
const emit = defineEmits(['updateUser', 'customEvent'])
const handleClick = () => {
// 触发事件,并传递数据
emit('updateUser', { id: 456, name: '李四' })
// 也可以触发不带参数的事件
emit('customEvent')
}
</script>父组件 (Parent.vue):
<template>
<div>
<ChildComponent
@updateUser="handleUserUpdate"
@customEvent="handleCustomEvent"
/>
<p>当前用户: {{ user.name }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const user = ref({ name: '王五', id: 789 })
const handleUserUpdate = (newUser) => {
// 接收子组件传递过来的数据
user.value = newUser
console.log('用户已更新:', newUser)
}
const handleCustomEvent = () => {
console.log('自定义事件被触发了')
}
</script>当组件嵌套层级很深,逐层传递 props 会非常繁琐时,可以使用 provide 和 inject。
使用 provide / inject 祖先组件提供数据,后代组件(无论多深)注入并使用。
祖先组件 (App.vue 或 Layout.vue):
<template>
<div>
<DeeplyNestedChild />
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
import DeeplyNestedChild from './components/DeeplyNestedChild.vue'
// 提供一个响应式数据
const theme = ref('dark')
const appConfig = {
apiUrl: 'https://api.example.com',
version: '1.0.0'
}
// 第一个参数是 key (推荐使用 Symbol 或字符串),第二个参数是提供的值
provide('THEME', theme)
provide('APP_CONFIG', appConfig)
</script>后代组件 (DeeplyNestedChild.vue):
浅色版本
<template>
<div :class="theme">
<p>当前主题: {{ theme }}</p>
<p>API 地址: {{ config.apiUrl }}</p>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据,第二个参数是默认值(可选但推荐)
const theme = inject('THEME', 'light')
const config = inject('APP_CONFIG', {})
</script>注意 如果 provide 的是 ref 或 reactive 对象,inject 得到的数据会保持响应性。 为避免命名冲突,建议使用 Symbol 作为 key:
浅色版本
// keys.js
export const THEME_KEY = Symbol()
export const CONFIG_KEY = Symbol()
// 在 provide 和 inject 时使用这些 Symbol虚拟滚动:长列表使用 vue-virtual-scroller 计算属性缓存:避免在模板中执行复杂计算 响应式优化:对大型对象使用 shallowRef
module.exports = {
extends: [
'plugin:vue/vue3-recommended',
'@vue/typescript/recommended'
],
rules: {
'vue/multi-word-component-names': 'off', // 允许单文件组件名
'@typescript-eslint/no-explicit-any': 'warn'
}
};{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}提交信息格式:<_type>: <_subject> 强制检查:使用 Husky + Commitlint
feat: 添加用户登录功能 fix: 修复表单验证错误 chore: 更新依赖版本
避免混用 Options API 和 Composition API
<script>
export default {
data() { return { count: 0 } } // 不要与 setup() 混用
}
</script>
<script setup>
const count = ref(0); // 冲突
</script>禁止直接修改 Props
<script setup>
const props = defineProps(['count']);
// 错误方式
props.count++;
// 正确方式:通过 emits
const emit = defineEmits(['update:count']);
emit('update:count', props.count + 1);
</script>避免深层响应式
// 性能开销大
const state = reactive({
user: {
profile: {
name: 'Alice'
}
}
});
// 优化:对大型对象使用 shallowRef
const largeData = shallowRef({ /* ... */ });遵循以上规范可显著提升代码可维护性和团队协作效率,建议结合 Vue Style Guide 官方文档使用。