Vue 2 Best Practices (Legacy & Maintenance)
🌟 技能核心:稳定维护与平滑过渡
本技能旨在指导开发者维护现有的 Vue 2 项目,编写清晰、可预测的 Options API 代码,并为未来迁移到 Vue 3 做准备。 核心原则:规范化 Options API、谨慎使用 Mixins、组件解耦。
🧠 Core Principles (核心原则)
- Options API 规范
-
Order of Options: 遵循一致的选项顺序:
-
name
-
components
-
props
-
data
-
computed
-
watch
-
lifecycle hooks (created, mounted, etc.)
-
methods
-
Data Function: data 必须始终是一个返回对象的函数,防止组件实例间状态污染。
-
Props Validation: 始终为 props 定义详细的类型验证和默认值。
- 逻辑复用 (Logic Reuse)
-
Mixins:
-
警告: 尽量减少 Mixins 的使用。它们会导致命名冲突和隐式依赖,使代码难以理解。
-
替代: 如果必须复用逻辑,考虑使用 HOC (Higher Order Components) 或 Scoped Slots (作用域插槽)。
-
规范: 如果使用 Mixin,必须加上明确的前缀,并在组件注释中说明来源。
-
Utility Functions: 将纯逻辑提取为独立的 JS 文件导入使用。
- 组件通信
-
Event Bus:
-
警告: 避免滥用全局 Event Bus (new Vue() ) 进行通信,这会导致事件流难以追踪。
-
替代: 使用 Vuex 或 Props/Events 进行父子通信。
-
Props Down, Events Up: 严格遵守单向数据流原则。不要直接修改 prop。
🧩 状态管理 (Vuex)
-
Vuex 3:
-
Modules: 始终使用 Namespaced Modules (namespaced: true ) 来组织 Store。
-
MapHelpers: 使用 mapState , mapGetters , mapActions 简化组件内的调用。
-
Mutations: 必须是同步的。异步逻辑放在 Actions 中。
-
Strict Mode: 在开发环境开启严格模式,确保状态只能通过 mutations 修改。
🚫 反模式 (Anti-Patterns)
-
❌ Arrow Functions in Methods: 不要在 methods 或生命周期钩子中使用箭头函数,这会导致 this 指向错误。
-
❌ Direct DOM Manipulation: 避免使用 jQuery 或直接操作 DOM,始终通过数据驱动视图。如果必须操作,使用 ref 。
-
❌ Over-reliance on Watchers: 优先使用 computed 属性来处理派生数据,而不是滥用 watch 。
-
❌ Implicit Global Variables: 避免直接在 Vue.prototype 上挂载过多全局变量。
🔄 迁移准备 (Migration Readiness)
-
Avoid Deprecated Features: 停止使用即将在 Vue 3 移除的特性(如 Filters, Inline Templates, $listeners )。
-
Composition API Plugin: 在 Vue 2.7+ 中,尝试引入 Composition API (<script setup> ),以便逐步过渡到 Vue 3 的写法。
📝 代码示例
- 规范的 Options API 组件
<template> <div class="user-card"> <h3>{{ formattedName }}</h3> <p>{{ user.email }}</p> <button @click="handleEdit">编辑</button> </div> </template>
<script> // ✅ 遵循选项顺序规范 export default { name: 'UserCard', // 1. name
components: { // 2. components EditButton },
props: { // 3. props - 带类型验证 user: { type: Object, required: true, validator: (value) => ['id', 'name', 'email'].every(key => key in value) } },
data() { // 4. data - 必须是函数 return { isEditing: false } },
computed: { // 5. computed formattedName() { return this.user.name.toUpperCase() } },
watch: { // 6. watch 'user.id': { handler(newId) { this.fetchUserData(newId) }, immediate: true } },
created() { // 7. 生命周期钩子 this.initializeComponent() },
mounted() { this.setupEventListeners() },
methods: { // 8. methods - 避免箭头函数 handleEdit() { this.$emit('edit', this.user.id) },
fetchUserData(id) {
// 数据获取逻辑
},
initializeComponent() {
// 初始化逻辑
},
setupEventListeners() {
// 事件监听
}
} } </script>
- Vuex 模块化 Store
// store/modules/user.js // ✅ 使用命名空间模块 export default { namespaced: true,
state: () => ({ user: null, token: null, loading: false }),
getters: { isLoggedIn: state => !!state.token, displayName: state => state.user?.name ?? '访客' },
// Mutations 必须是同步的 mutations: { SET_USER(state, user) { state.user = user }, SET_TOKEN(state, token) { state.token = token }, SET_LOADING(state, loading) { state.loading = loading } },
// Actions 处理异步逻辑 actions: { async login({ commit }, credentials) { commit('SET_LOADING', true) try { const { user, token } = await authService.login(credentials) commit('SET_USER', user) commit('SET_TOKEN', token) return { success: true } } catch (error) { return { success: false, error: error.message } } finally { commit('SET_LOADING', false) } },
logout({ commit }) {
commit('SET_USER', null)
commit('SET_TOKEN', null)
}
} }
<!-- 组件中使用 Vuex --> <template> <div v-if="isLoggedIn"> <p>欢迎, {{ displayName }}</p> <button @click="logout">退出</button> </div> </template>
<script> import { mapState, mapGetters, mapActions } from 'vuex'
export default { name: 'UserStatus',
computed: { // ✅ 使用 map 辅助函数 ...mapState('user', ['loading']), ...mapGetters('user', ['isLoggedIn', 'displayName']) },
methods: { ...mapActions('user', ['logout']) } } </script>
- 避免 Mixin,使用工具函数
// ❌ 避免 Mixin const userMixin = { data() { return { user: null } }, methods: { fetchUser() { /* ... */ } } }
// ✅ 使用工具函数 // utils/user.js export function createUserService() { return { user: null, async fetchUser(id) { this.user = await api.getUser(id) return this.user } } }
// 组件中使用 import { createUserService } from '@/utils/user'
export default { data() { return { userService: createUserService() } }, async created() { await this.userService.fetchUser(this.userId) } }
- Props 验证最佳实践
<script> export default { props: { // ✅ 基础类型验证 title: String,
// ✅ 多种可能的类型
value: [String, Number],
// ✅ 必填字段
userId: {
type: [String, Number],
required: true
},
// ✅ 带默认值
size: {
type: String,
default: 'medium',
validator: (value) => ['small', 'medium', 'large'].includes(value)
},
// ✅ 对象/数组默认值使用工厂函数
config: {
type: Object,
default: () => ({
theme: 'light',
locale: 'zh-CN'
})
},
// ✅ 自定义验证
email: {
type: String,
validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}
} } </script>
- Vue 2.7+ Composition API 迁移准备
<template> <div> <p>计数: {{ count }}</p> <button @click="increment">+1</button> </div> </template>
<script> // Vue 2.7+ 可以使用 Composition API import { ref, computed, onMounted } from 'vue'
export default { name: 'Counter',
// ✅ 渐进式迁移:setup 函数 setup() { const count = ref(0) const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('Counter mounted')
})
return {
count,
doubled,
increment
}
} } </script>
🎨 常用指令示例
规范化 Options 顺序
/vue-coder 重新排列这个组件的选项顺序,使其符合最佳实践。
移除 Mixin
/vue-coder 分析这个组件使用的 Mixin,尝试将其重构为普通的函数导入或 HOC。
Vuex 模块化
/vue-coder 将这个庞大的 Vuex store 拆分为独立的命名空间模块。