Vue 2与Vue 3 最佳实践与代码规范建议
2023-05-015.5k 阅读
Vue 2 最佳实践
组件设计与架构
- 单一职责原则
- 在 Vue 2 中,组件应遵循单一职责原则。每个组件应该只负责一件事情,这样可以提高组件的可维护性和复用性。例如,假设有一个用户信息展示组件
UserInfo.vue
,它应该只专注于展示用户的基本信息,如姓名、年龄等,而不应该包含用户登录、注册等与展示无关的逻辑。 - 代码示例:
- 在 Vue 2 中,组件应遵循单一职责原则。每个组件应该只负责一件事情,这样可以提高组件的可维护性和复用性。例如,假设有一个用户信息展示组件
<template>
<div>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
age: 25
}
};
}
};
</script>
- 组件层次结构
- 合理规划组件的层次结构至关重要。对于大型应用,通常会采用父子组件的嵌套方式来构建页面。例如,在一个电商应用中,可能有一个
ProductList
组件作为父组件,它包含多个ProductItem
子组件来展示商品列表。父组件负责管理商品数据的获取和传递,子组件负责单个商品的展示和交互。 - 代码示例:
ProductList.vue
- 合理规划组件的层次结构至关重要。对于大型应用,通常会采用父子组件的嵌套方式来构建页面。例如,在一个电商应用中,可能有一个
<template>
<div>
<ProductItem v - for="product in products" :key="product.id" :product="product" />
</div>
</template>
<script>
import ProductItem from './ProductItem.vue';
export default {
components: {
ProductItem
},
data() {
return {
products: [
{ id: 1, name: '商品1', price: 100 },
{ id: 2, name: '商品2', price: 200 }
]
};
}
};
</script>
ProductItem.vue
<template>
<div>
<p>{{ product.name }}</p>
<p>价格: {{ product.price }}</p>
</div>
</template>
<script>
export default {
props: ['product']
};
</script>
数据管理
- 使用 data 选项
- 在 Vue 2 组件中,
data
选项是存储组件状态的地方。需要注意的是,data
必须是一个函数,这样每个组件实例才能拥有独立的数据副本。例如,在一个计数器组件中: - 代码示例:
- 在 Vue 2 组件中,
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
</script>
- 计算属性与方法的选择
- 计算属性是基于它们的依赖进行缓存的,只有当依赖发生变化时才会重新计算。而方法在每次调用时都会重新执行。例如,在一个购物车组件中,如果需要计算商品的总价,使用计算属性会更合适。
- 代码示例:
<template>
<div>
<ul>
<li v - for="item in cartItems" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
<p>总价: {{ totalPrice }}</p>
</div>
</template>
<script>
export default {
data() {
return {
cartItems: [
{ id: 1, name: '商品1', price: 100 },
{ id: 2, name: '商品2', price: 200 }
]
};
},
computed: {
totalPrice() {
return this.cartItems.reduce((acc, item) => acc + item.price, 0);
}
}
};
</script>
事件处理
- 绑定原生 DOM 事件
- 在 Vue 2 中,可以使用
v - on
指令(缩写为@
)来绑定原生 DOM 事件。例如,在一个按钮点击事件中: - 代码示例:
- 在 Vue 2 中,可以使用
<template>
<div>
<button @click="handleClick">点击我</button>
</div>
</template>
<script>
export default {
methods: {
handleClick() {
console.log('按钮被点击了');
}
}
};
</script>
- 组件间事件通信
- 父子组件通信:父组件可以通过
props
向子组件传递数据,子组件通过$emit
触发自定义事件向父组件传递数据。例如,在一个Child
组件中,点击按钮向父组件传递一个消息。 - 代码示例:
Parent.vue
- 父子组件通信:父组件可以通过
<template>
<div>
<Child @child - event="handleChildEvent" />
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child
},
methods: {
handleChildEvent(message) {
console.log('子组件传来的消息:', message);
}
}
};
</script>
Child.vue
<template>
<div>
<button @click="sendMessage">发送消息</button>
</div>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('child - event', '这是子组件的消息');
}
}
};
</script>
- 非父子组件通信:对于非父子组件通信,可以使用一个事件总线(event bus)。创建一个空的 Vue 实例作为事件总线,然后在各个组件中通过这个实例来触发和监听事件。
- 代码示例:
eventBus.js
import Vue from 'vue';
export const eventBus = new Vue();
ComponentA.vue
<template>
<div>
<button @click="sendMessage">向 ComponentB 发送消息</button>
</div>
</template>
<script>
import { eventBus } from './eventBus.js';
export default {
methods: {
sendMessage() {
eventBus.$emit('message - from - a', '这是来自 ComponentA 的消息');
}
}
};
</script>
ComponentB.vue
<template>
<div>等待接收消息</div>
</template>
<script>
import { eventBus } from './eventBus.js';
export default {
created() {
eventBus.$on('message - from - a', (message) => {
console.log('收到来自 ComponentA 的消息:', message);
});
}
};
</script>
指令使用
- v - if 与 v - show
v - if
是真正的条件渲染,它会根据条件动态地添加或移除 DOM 元素。v - show
则是通过 CSS 的display
属性来控制元素的显示与隐藏。一般来说,如果元素在运行时很少改变显示状态,使用v - if
更合适;如果元素需要频繁切换显示状态,使用v - show
性能更好。- 代码示例:
- 使用
v - if
<template>
<div>
<button @click="toggle">切换</button>
<p v - if="isVisible">这是使用 v - if 控制的内容</p>
</div>
</template>
<script>
export default {
data() {
return {
isVisible: true
};
},
methods: {
toggle() {
this.isVisible =!this.isVisible;
}
}
};
</script>
- 使用
v - show
<template>
<div>
<button @click="toggle">切换</button>
<p v - show="isVisible">这是使用 v - show 控制的内容</p>
</div>
</template>
<script>
export default {
data() {
return {
isVisible: true
};
},
methods: {
toggle() {
this.isVisible =!this.isVisible;
}
}
};
</script>
- v - for 与 key
- 在使用
v - for
指令进行列表渲染时,必须为每个列表项提供一个唯一的key
值。key
主要用于 Vue 进行虚拟 DOM 对比时,更高效地更新和复用 DOM 元素。例如,在渲染一个用户列表时: - 代码示例:
- 在使用
<template>
<div>
<ul>
<li v - for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
};
}
};
</script>
Vue 3 最佳实践
组合式 API 应用
- setup 函数基础
- 在 Vue 3 中,
setup
函数是组合式 API 的入口点。它在组件创建之前被调用,并且在data
、computed
和methods
等选项之前执行。setup
函数接收两个参数:props
和context
。props
是传递给组件的属性,context
包含attrs
、slots
和emit
等上下文信息。 - 代码示例:
- 在 Vue 3 中,
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return {
count,
increment
};
}
};
</script>
- 使用 reactive 定义响应式数据
reactive
函数用于创建一个响应式对象。与ref
不同,ref
主要用于创建单个响应式值,而reactive
适用于创建复杂的响应式对象。例如,在一个用户信息组件中:- 代码示例:
<template>
<div>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<button @click="updateUser">更新用户信息</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const user = reactive({
name: '张三',
age: 25
});
const updateUser = () => {
user.name = '李四';
user.age = 26;
};
return {
user,
updateUser
};
}
};
</script>
- 计算属性与方法在组合式 API 中的实现
- 计算属性:在组合式 API 中,可以使用
computed
函数来定义计算属性。例如,计算购物车商品总价: - 代码示例:
- 计算属性:在组合式 API 中,可以使用
<template>
<div>
<ul>
<li v - for="item in cartItems" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
<p>总价: {{ totalPrice }}</p>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const cartItems = ref([
{ id: 1, name: '商品1', price: 100 },
{ id: 2, name: '商品2', price: 200 }
]);
const totalPrice = computed(() => {
return cartItems.value.reduce((acc, item) => acc + item.price, 0);
});
return {
cartItems,
totalPrice
};
}
};
</script>
- 方法:在
setup
函数中定义的函数就是组件的方法。例如,在一个表单提交组件中: - 代码示例:
<template>
<div>
<input v - model="formData.username" placeholder="用户名" />
<input v - model="formData.password" placeholder="密码" type="password" />
<button @click="submitForm">提交</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const formData = reactive({
username: '',
password: ''
});
const submitForm = () => {
console.log('提交的数据:', formData);
};
return {
formData,
submitForm
};
}
};
</script>
生命周期钩子函数
- 在组合式 API 中的使用
- 在 Vue 3 组合式 API 中,生命周期钩子函数以
on + 钩子函数名
的形式使用。例如,onMounted
用于在组件挂载后执行某些操作,onUnmounted
用于在组件卸载后执行操作。 - 代码示例:
- 在 Vue 3 组合式 API 中,生命周期钩子函数以
<template>
<div>组件示例</div>
</template>
<script>
import { onMounted, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('组件已挂载');
});
onUnmounted(() => {
console.log('组件已卸载');
});
}
};
</script>
- 与 Vue 2 生命周期钩子的对应关系
- Vue 2 的
created
对应 Vue 3 组合式 API 中的setup
函数内部逻辑执行开始;Vue 2 的mounted
对应onMounted
;Vue 2 的beforeUpdate
对应onBeforeUpdate
;Vue 2 的updated
对应onUpdated
;Vue 2 的beforeDestroy
对应onBeforeUnmount
;Vue 2 的destroyed
对应onUnmounted
。
- Vue 2 的
响应式原理与优化
- 理解 Vue 3 响应式原理
- Vue 3 使用了
Proxy
来实现响应式系统,相比 Vue 2 的Object.defineProperty
,Proxy
具有更强大的功能和性能优势。Proxy
可以直接代理整个对象,而不需要对对象的每个属性进行遍历和劫持。例如,在创建一个响应式对象时: - 代码示例:
- Vue 3 使用了
import { reactive } from 'vue';
const user = reactive({
name: '张三',
age: 25
});
// 当访问或修改 user 的属性时,Vue 3 会通过 Proxy 捕获并进行响应式处理
- 性能优化
- 使用
shallowRef
和shallowReactive
:如果数据结构非常复杂,并且不需要对所有深层次的数据进行响应式处理,可以使用shallowRef
和shallowReactive
。shallowRef
只对自身的值变化做出响应,不会对其内部对象的属性变化做出响应;shallowReactive
只对对象的直接属性进行响应式处理,不会递归处理深层次属性。 - 代码示例:
- 使用
shallowRef
- 使用
<template>
<div>
<p>{{ complexData.value.name }}</p>
<button @click="updateComplexData">更新数据</button>
</div>
</template>
<script>
import { shallowRef } from 'vue';
export default {
setup() {
const complexData = shallowRef({
name: '初始值',
subData: {
detail: '一些细节'
}
});
const updateComplexData = () => {
// 这不会触发视图更新,因为 shallowRef 只对自身值变化响应
complexData.value.subData.detail = '新细节';
// 这会触发视图更新
complexData.value = {
name: '新值',
subData: {
detail: '一些细节'
}
};
};
return {
complexData,
updateComplexData
};
}
};
</script>
- 使用
shallowReactive
<template>
<div>
<p>{{ user.name }}</p>
<p>{{ user.subUser.name }}</p>
<button @click="updateUser">更新用户</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue';
export default {
setup() {
const user = shallowReactive({
name: '张三',
subUser: {
name: '子用户'
}
});
const updateUser = () => {
// 这不会触发视图更新,因为 shallowReactive 不对深层次属性响应
user.subUser.name = '新子用户';
// 这会触发视图更新
user.name = '李四';
};
return {
user,
updateUser
};
}
};
</script>
Vue 2 代码规范建议
组件命名规范
- 文件命名
- 组件文件命名应采用
PascalCase
(大驼峰命名法)。例如,UserInfo.vue
、ProductList.vue
等。这样命名可以清晰地表明这是一个组件,并且在导入和使用时也更容易识别。
- 组件文件命名应采用
- 组件内 name 选项
- 组件内部的
name
选项也应采用PascalCase
。这有助于在调试工具中更好地识别组件,同时在递归组件中也需要使用name
选项来进行正确的递归调用。 - 代码示例:
- 组件内部的
<template>
<div>组件内容</div>
</template>
<script>
export default {
name: 'MyComponent',
data() {
return {};
}
};
</script>
样式规范
- 局部样式
- 对于组件内部的样式,应使用
<style scoped>
。这样可以确保样式只作用于当前组件,避免样式污染全局。例如: - 代码示例:
- 对于组件内部的样式,应使用
<template>
<div class="my - component">
<p>组件内容</p>
</div>
</template>
<style scoped>
.my - component {
background - color: lightblue;
}
</style>
- 使用 BEM 命名规范
- 在编写 CSS 类名时,推荐使用 BEM(Block - Element - Modifier)命名规范。例如,在一个按钮组件中,按钮整体是一个块(block),按钮的不同状态(如禁用状态)可以作为修饰符(modifier)。
- 代码示例:
<template>
<button class="button button--disabled">按钮</button>
</template>
<style scoped>
.button {
background - color: blue;
color: white;
}
.button--disabled {
background - color: gray;
cursor: not - allowed;
}
</style>
代码结构规范
- 选项顺序
- 在 Vue 2 组件中,推荐按照以下顺序排列选项:
name
、components
、directives
、props
、data
、computed
、watch
、created
、mounted
、updated
、beforeDestroy
、destroyed
、methods
。这样的顺序可以使组件代码结构更清晰,易于阅读和维护。 - 代码示例:
- 在 Vue 2 组件中,推荐按照以下顺序排列选项:
<template>
<div>组件示例</div>
</template>
<script>
export default {
name: 'MyComponent',
components: {},
directives: {},
props: {},
data() {
return {};
},
computed: {},
watch: {},
created() {},
mounted() {},
updated() {},
beforeDestroy() {},
destroyed() {},
methods: {}
};
</script>
- 注释规范
- 对复杂的逻辑代码段、组件的功能、
props
的含义等都应添加注释。单行注释使用//
,多行注释使用/*... */
。例如: - 代码示例:
- 对复杂的逻辑代码段、组件的功能、
<template>
<div>
<!-- 这里是一个用于展示用户信息的组件 -->
<p>{{ user.name }}</p>
</div>
</template>
<script>
export default {
data() {
return {
// 用户信息对象
user: {
name: '张三'
}
};
}
};
</script>
Vue 3 代码规范建议
组合式 API 代码规范
- 变量与函数命名
- 在
setup
函数中定义的变量和函数应采用camelCase
(小驼峰命名法)。例如,count
、incrementCount
等。这样的命名方式与 JavaScript 的命名习惯保持一致,易于理解和维护。
- 在
- 逻辑拆分与组织
- 对于复杂的组件逻辑,可以将相关的逻辑拆分成多个函数或模块。例如,在一个包含用户登录、注册和密码找回功能的组件中,可以将登录逻辑放在
useLogin.js
模块中,注册逻辑放在useRegister.js
模块中,然后在setup
函数中引入并使用。 - 代码示例:
useLogin.js
- 对于复杂的组件逻辑,可以将相关的逻辑拆分成多个函数或模块。例如,在一个包含用户登录、注册和密码找回功能的组件中,可以将登录逻辑放在
import { ref } from 'vue';
export const useLogin = () => {
const username = ref('');
const password = ref('');
const login = () => {
// 登录逻辑
console.log('登录中,用户名:', username.value, '密码:', password.value);
};
return {
username,
password,
login
};
};
- 组件
LoginComponent.vue
<template>
<div>
<input v - model="username" placeholder="用户名" />
<input v - model="password" placeholder="密码" type="password" />
<button @click="login">登录</button>
</div>
</template>
<script>
import { useLogin } from './useLogin.js';
export default {
setup() {
const { username, password, login } = useLogin();
return {
username,
password,
login
};
}
};
</script>
模板语法规范
- v - if 和 v - for 的使用顺序
- 在 Vue 3 模板中,如果同时使用
v - if
和v - for
,应该将v - if
放在外层元素上。这是因为v - for
的优先级高于v - if
,如果将v - if
放在v - for
内部,会在每次循环时都进行条件判断,影响性能。 - 代码示例:
- 在 Vue 3 模板中,如果同时使用
<template>
<div>
<div v - if="shouldShowList">
<ul>
<li v - for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const shouldShowList = ref(true);
const items = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
]);
return {
shouldShowList,
items
};
}
};
</script>
- 指令缩写使用
- 推荐使用指令的缩写形式,如
@
代替v - on
,:
代替v - bind
。这样可以使模板代码更简洁易读。例如: - 代码示例:
- 推荐使用指令的缩写形式,如
<template>
<div>
<a :href="link" @click="handleClick">点击链接</a>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const link = ref('https://example.com');
const handleClick = () => {
console.log('链接被点击');
};
return {
link,
handleClick
};
}
};
</script>
整体项目结构规范
- 文件夹结构
- 对于 Vue 3 项目,推荐采用清晰的文件夹结构。例如,可以将组件分为
components
文件夹存放基础组件,views
文件夹存放页面级组件;将 API 相关的代码放在api
文件夹中;将样式文件放在styles
文件夹中。这样的结构有助于项目的管理和维护。 - 示例文件夹结构:
- 对于 Vue 3 项目,推荐采用清晰的文件夹结构。例如,可以将组件分为
project - root
├── src
│ ├── api
│ │ ├── userApi.js
│ │ ├── productApi.js
│ ├── components
│ │ ├── Button.vue
│ │ ├── Input.vue
│ ├── views
│ │ ├── Home.vue
│ │ ├── About.vue
│ ├── styles
│ │ ├── main.css
│ │ ├── variables.css
│ ├── App.vue
│ ├── main.js
├── public
├── package.json
- 使用 TypeScript 增强代码健壮性
- 如果项目规模较大或对代码类型安全要求较高,可以使用 TypeScript 与 Vue 3 结合。Vue 3 对 TypeScript 有良好的支持,可以在组件中定义
props
的类型、函数的参数和返回值类型等,从而提高代码的可维护性和健壮性。 - 代码示例:
- 如果项目规模较大或对代码类型安全要求较高,可以使用 TypeScript 与 Vue 3 结合。Vue 3 对 TypeScript 有良好的支持,可以在组件中定义
<template>
<div>
<p>{{ user.name }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
interface User {
name: string;
}
export default defineComponent({
data() {
return {
user: {
name: '张三'
} as User
};
}
});
</script>