Skip to content

Vue3 规范

学习地址

基本规则

  1. 推荐使用 <script setup> 语法糖,简洁高效
  2. 推荐使用 TypeScript 进行开发
  3. 推荐使用组合式 API(Composition API),避免使用选项式 API(Options API)
  4. 禁止使用 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.tsuseLoading.ts
  • 函数名以 use 开头,如 useUser()useLoading()
  • 统一放在 composableshooks 目录下

模板语法规范

指令缩写

统一使用指令缩写:

html
<!-- 推荐 -->
<input :value="name" @input="handleInput" #header />

<!-- 不推荐 -->
<input v-bind:value="name" v-on:input="handleInput" v-slot:header />

v-for 与 v-if

禁止在同一元素上同时使用 v-forv-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;

样式规范

  1. 组件样式必须使用 scoped,防止样式污染
  2. 深度选择器使用 :deep() 语法
  3. 全局样式统一管理在 styles 目录下
vue
<style scoped lang="scss">
.container {
  padding: 16px;

  // 深度选择器 - 修改子组件样式
  :deep(.el-input) {
    width: 200px;
  }
}
</style>