引言
Vue3 已经成为了现代前端开发的主流选择,其 Composition API 和更好的性能优化为开发者带来了全新的体验。然而,在从 Vue2 迁移或直接学习 Vue3 的过程中,很多开发者会遇到各种语法和概念上的困惑。本文将总结 Vue3 开发中常见的语法注意问题,帮助大家避免陷阱,写出更优雅的代码。
1. 响应式系统的变化
1.1 ref 与 reactive 的正确使用
javascript
// ✅ 正确用法
import { ref, reactive } from 'vue'
// 基本类型使用 ref
const count = ref(0)
const name = ref('Vue3')
// 对象类型可以使用 reactive
const user = reactive({
name: 'John',
age: 25
})
// ❌ 避免这样使用
const user = ref({
name: 'John',
age: 25
})
// 访问时需要 .value,但在模板中不需要,容易混淆
// ✅ 对于对象,推荐统一使用 reactive
const state = reactive({
count: 0,
user: {
name: 'John',
age: 25
}
})
1.2 响应式丢失问题
javascript
// ❌ 响应式丢失
const state = reactive({ count: 0 })
let { count } = state // 解构会丢失响应式
// ✅ 使用 toRefs 保持响应式
const state = reactive({
count: 0,
name: 'Vue3'
})
const { count, name } = toRefs(state)
// ✅ 或者使用 computed
const count = computed(() => state.count)
1.3 数组的响应式更新
javascript
const list = reactive([])
// ❌ 这样不会触发更新
list = [1, 2, 3]
// ✅ 正确的方式
list.push(1, 2, 3)
// 或者
list.splice(0, list.length, 1, 2, 3)
2. Composition API 的注意事项
2.1 setup 函数的正确使用
javascript
// ✅ 推荐使用 setup 语法糖
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
onMounted(() => {
console.log('组件已挂载')
})
</script>
// ❌ 传统写法较繁琐
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>
2.2 生命周期钩子的变化
javascript
import { onMounted, onUpdated, onUnmounted } from 'vue'
// ✅ Composition API 生命周期
setup() {
onMounted(() => {
console.log('组件挂载')
})
onUpdated(() => {
console.log('组件更新')
})
onUnmounted(() => {
console.log('组件卸载')
})
}
// ❌ 不要在异步函数中直接使用生命周期钩子
// 这样不会正常工作
setTimeout(() => {
onMounted(() => {
// 这里的代码不会执行
})
}, 1000)
2.3 计算属性和监听器
javascript
import { computed, watch, watchEffect } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
// ✅ 正确的 watch 使用
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
// ✅ watchEffect 自动收集依赖
watchEffect(() => {
console.log(`count 的值是: ${count.value}`)
})
// ❌ 避免在 watch 中直接修改被监听的数据
watch(count, (newVal) => {
count.value = newVal * 2 // 这会导致无限循环
})
3. 模板语法的变化
3.1 v-model 的升级
vue
<!-- Vue2 写法 -->
<!-- <input v-model="text" /> -->
<!-- Vue3 写法 -->
<template>
<!-- 默认用法 -->
<input v-model="text" />
<!-- 多个 v-model -->
<CustomComponent
v-model:title="pageTitle"
v-model:content="pageContent"
/>
<!-- 自定义修饰符 -->
<CustomComponent v-model.capitalize="text" />
</template>
3.2 片段支持
vue
<!-- ✅ Vue3 支持多个根元素 -->
<template>
<header>头部</header>
<main>主要内容</main>
<footer>底部</footer>
</template>
<!-- ❌ Vue2 只能有一个根元素 -->
<!-- 这会报错 -->
<template>
<div>元素1</div>
<div>元素2</div>
</template>
3.3 事件监听器的变化
vue
<template>
<!-- ✅ Vue3 支持多个 v-on 修饰符 -->
<button @click.once="handleClick">只触发一次</button>
<!-- ✅ 事件验证 -->
<button @click="handleSubmit($event, 'submit')">提交</button>
<!-- ❌ 不再支持 .native 修饰符 -->
<!-- <CustomComponent @click.native="handleClick" /> -->
<!-- ✅ 使用 emits 定义 -->
<CustomComponent @custom-click="handleClick" />
</template>
4. 组件开发的注意事项
4.1 defineProps 和 defineEmits
vue
<script setup>
// ✅ 推荐使用 defineProps 和 defineEmits
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 定义事件
const emit = defineEmits(['update:title', 'change'])
const handleClick = () => {
emit('update:title', '新标题')
emit('change', 123)
}
</script>
<!-- ❌ 不推荐使用 this -->
<script>
export default {
methods: {
handleClick() {
this.$emit('change') // 在 setup 中不可用
}
}
}
</script>
4.2 组件引用
vue
<script setup>
import { ref, onMounted } from 'vue'
// ✅ 使用 ref 引用组件
const myComponent = ref(null)
onMounted(() => {
// 访问组件实例
console.log(myComponent.value)
})
// ✅ 使用 defineExpose 暴露方法
defineExpose({
someMethod() {
console.log('暴露的方法')
}
})
</script>
<template>
<ChildComponent ref="myComponent" />
</template>
5. TypeScript 集成注意事项
5.1 类型定义
typescript
<script setup lang="ts">
import { ref } from 'vue'
// ✅ 正确的类型定义
interface User {
name: string
age: number
}
const user = ref<User>({
name: 'John',
age: 25
})
// ✅ 定义 props 类型
interface Props {
title: string
count?: number
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// ✅ 定义 emits 类型
const emit = defineEmits<{
(e: 'update:title', value: string): void
(e: 'change', value: number): void
}>()
</script>
5.2 组合式函数的类型安全
typescript
// ✅ 类型安全的组合式函数
import { ref, Ref } from 'vue'
export function useCounter(initialValue: number = 0) {
const count: Ref<number> = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
increment,
decrement,
reset
}
}
6. 性能优化相关
6.1 避免不必要的重新渲染
vue
<script setup>
import { shallowRef, shallowReactive } from 'vue'
// ✅ 对于大型对象,使用 shallowReactive
const largeObject = shallowReactive({
// 只有第一层属性是响应式的
nested: {
data: '不会触发深层更新'
}
})
// ✅ 对于组件引用,使用 shallowRef
const childComponent = shallowRef(null)
</script>
6.2 合理使用 computed
javascript
import { computed } from 'vue'
const list = ref([1, 2, 3, 4, 5])
// ❌ 避免在模板中直接进行复杂计算
// {{ list.filter(x => x > 2).map(x => x * 2) }}
// ✅ 使用 computed 缓存结果
const filteredList = computed(() =>
list.value.filter(x => x > 2).map(x => x * 2)
)
7. 常见错误和解决方案
7.1 异步组件加载
javascript
// ✅ 正确的异步组件定义
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
// ✅ 带有加载状态的异步组件
const AsyncComponentWithLoading = defineAsyncComponent({
loader: () => import('./AsyncComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
7.2 样式作用域
vue
<template>
<div class="example">
<span>内容</span>
</div>
</template>
<style scoped>
/* ✅ 作用域样式 */
.example {
color: red;
}
/* ❌ 深度选择器语法变化 */
/* Vue2: /deep/ 或 >>> */
/* Vue3: 使用 :deep() */
:deep(.child-class) {
color: blue;
}
/* ✅ 全局样式 */
:global(.global-class) {
font-size: 16px;
}
</style>
结语
Vue3 带来了许多强大的新特性,但也需要开发者注意相应的语法变化和最佳实践。通过理解这些注意问题,我们可以避免常见的陷阱,写出更健壮、可维护的 Vue3 应用。
记住,好的代码不仅仅是能运行,更重要的是易于理解和维护。希望本文能帮助你在 Vue3 的开发道路上走得更加顺畅!