Appearance
Vue3 规范
学习地址
基本规则
- 推荐使用
<script setup>语法糖,简洁高效 - 推荐使用 TypeScript 进行开发
- 推荐使用组合式 API(Composition API),避免使用选项式 API(Options API)
- 禁止使用
mixins,使用组合式函数(Composables)替代
组件规范
组件命名
- 文件命名:单文件组件使用 PascalCase 或 kebab-case,推荐 PascalCase
- 组件名:始终使用多词命名,避免与 HTML 元素冲突
✅ 推荐:UserProfile.vue、SearchInput.vue
❌ 不推荐:User.vue、Header.vue(可能与 HTML 标签冲突)组件模板
vue
<!-- 推荐的组件结构 -->
<script setup lang="ts">
import { ref, computed } from "vue";
// Props 定义
interface Props {
title: string;
count?: number;
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
});
// Emits 定义
const emit = defineEmits<{
(e: "update", value: string): void;
(e: "delete", id: number): void;
}>();
// 响应式数据
const isLoading = ref(false);
// 计算属性
const doubleCount = computed(() => props.count * 2);
// 方法
const handleClick = () => {
emit("update", "new value");
};
</script>
<template>
<div class="user-profile">
<h2>{{ title }}</h2>
<span>{{ doubleCount }}</span>
<button @click="handleClick">更新</button>
</div>
</template>
<style scoped lang="scss">
.user-profile {
padding: 16px;
}
</style><script setup> 代码组织顺序
在 <script setup> 中,按以下顺序组织代码:
vue
<script setup lang="ts">
// 1. 导入
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useUserStore } from "@/store/modules/user";
import UserCard from "./components/UserCard.vue";
import type { UserInfo } from "@/types";
// 2. Props 和 Emits
const props = defineProps<{ id: string }>();
const emit = defineEmits<{ (e: "refresh"): void }>();
// 3. 组合式函数(Composables)
const router = useRouter();
const userStore = useUserStore();
// 4. 响应式数据
const userInfo = ref<UserInfo | null>(null);
const isLoading = ref(false);
// 5. 计算属性
const fullName = computed(() => {
return `${userInfo.value?.firstName} ${userInfo.value?.lastName}`;
});
// 6. 方法
const fetchUserInfo = async () => {
isLoading.value = true;
try {
userInfo.value = await userStore.getUserById(props.id);
} finally {
isLoading.value = false;
}
};
const handleEdit = () => {
router.push(`/user/edit/${props.id}`);
};
// 7. 生命周期
onMounted(() => {
fetchUserInfo();
});
</script>组合式函数(Composables)
使用组合式函数替代 Vue2 中的 mixins,实现逻辑复用:
ts
// composables/useLoading.ts
import { ref } from "vue";
export function useLoading() {
const isLoading = ref(false);
const startLoading = () => {
isLoading.value = true;
};
const stopLoading = () => {
isLoading.value = false;
};
const withLoading = async <T>(fn: () => Promise<T>): Promise<T> => {
startLoading();
try {
return await fn();
} finally {
stopLoading();
}
};
return {
isLoading,
startLoading,
stopLoading,
withLoading,
};
}vue
<!-- 使用组合式函数 -->
<script setup lang="ts">
import { useLoading } from "@/composables/useLoading";
const { isLoading, withLoading } = useLoading();
const fetchData = () =>
withLoading(async () => {
// 请求数据
});
</script>
<template>
<Loading v-if="isLoading" />
<DataList v-else />
</template>组合式函数命名规范
- 文件名以
use开头,如useUser.ts、useLoading.ts - 函数名以
use开头,如useUser()、useLoading() - 统一放在
composables或hooks目录下
模板语法规范
指令缩写
统一使用指令缩写:
html
<!-- 推荐 -->
<input :value="name" @input="handleInput" #header />
<!-- 不推荐 -->
<input v-bind:value="name" v-on:input="handleInput" v-slot:header />v-for 与 v-if
禁止在同一元素上同时使用 v-for 和 v-if:
html
<!-- 推荐:使用 computed 过滤 -->
<template>
<li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</template>
<script setup>
const activeUsers = computed(() => users.value.filter((u) => u.isActive));
</script>
<!-- 不推荐 -->
<li v-for="user in users" :key="user.id" v-if="user.isActive">
{{ user.name }}
</li>组件属性顺序
html
<MyComponent
ref="myRef"
class="my-class"
:id="componentId"
v-model="modelValue"
:prop-a="valueA"
:prop-b="valueB"
@click="handleClick"
@custom-event="handleEvent"
/>状态管理(Pinia)
Vue3 推荐使用 Pinia 作为状态管理方案:
ts
// store/modules/user.ts
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import type { UserInfo } from "@/types";
export const useUserStore = defineStore("user", () => {
// state
const userInfo = ref<UserInfo | null>(null);
const token = ref("");
// getters
const isLogin = computed(() => !!token.value);
const userName = computed(() => userInfo.value?.name ?? "");
// actions
const login = async (username: string, password: string) => {
// 登录逻辑
};
const logout = () => {
userInfo.value = null;
token.value = "";
};
return {
userInfo,
token,
isLogin,
userName,
login,
logout,
};
});注意
- 推荐使用 Setup Store 语法(组合式写法),与组件
<script setup>风格一致 - Store 文件按模块拆分,放在
store/modules/目录下 - Store 命名以
use开头,以Store结尾,如useUserStore
路由规范
ts
// router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import type { RouteRecordRaw } from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "Layout",
component: () => import("@/layout/index.vue"),
children: [
{
path: "",
name: "Home",
component: () => import("@/pages/home/index.vue"),
meta: { title: "首页", requiresAuth: false },
},
],
},
{
path: "/login",
name: "Login",
component: () => import("@/pages/login/index.vue"),
meta: { title: "登录", requiresAuth: false },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;样式规范
- 组件样式必须使用
scoped,防止样式污染 - 深度选择器使用
:deep()语法 - 全局样式统一管理在
styles目录下
vue
<style scoped lang="scss">
.container {
padding: 16px;
// 深度选择器 - 修改子组件样式
:deep(.el-input) {
width: 200px;
}
}
</style>