Vue 3.3带来了很多新特性,但实战中也有很多坑。

本文分享我在Vue 3.3+实战中踩过的坑,包括Composition API、响应式、组件通信、TypeScript、构建部署,以及解决方法。

一、我的Vue 3经历

1. 从Vue 2到Vue 3

我从Vue 2转到Vue 3,经历了一个适应过程。

  • Vue 2用Options API
  • Vue 3推荐Composition API
  • 刚开始不习惯
  • 慢慢发现Composition API的好处
  • 现在已经离不开了

Vue 3,是一次大的升级。

2. Vue 3.3的新特性

Vue 3.3带来了很多新特性:

  • defineProps支持泛型
  • defineEmits更简洁
  • defineOptions
  • 响应式props解构
  • 更好的TypeScript支持

这些新特性,让开发更高效。

3. 踩过的坑

但实战中,我踩过很多坑。

  • 响应式的坑
  • 组件通信的坑
  • TypeScript的坑
  • 构建部署的坑
  • 性能优化的坑

每个坑,都是一次学习。

二、Composition API的坑

1. 坑一:setup中this不可用

第一个坑:setup中this不可用。

  • Vue 2中用this
  • Vue 3的setup中没有this
  • 刚开始很不习惯
  • 不知道怎么获取实例
  • 后来学会了用getCurrentInstance

解决方法:

import { getCurrentInstance } from 'vue'

const instance = getCurrentInstance()
// instance.proxy 相当于this

但不建议滥用,大部分情况不需要this。

2. 坑二:ref和reactive分不清

第二个坑:ref和reactive分不清。

  • 什么时候用ref
  • 什么时候用reactive
  • 刚开始很混乱
  • 后来总结了规律

我的经验:

  • 基本类型用ref
  • 对象类型用reactive
  • 但ref也可以包对象
  • 统一用ref也可以
  • 关键是要一致

3. 坑三:ref需要.value

第三个坑:ref需要.value。

  • 在script中要写.value
  • 在template中不用
  • 经常忘记写.value
  • 导致bug

解决方法:

  • 养成习惯
  • 用TypeScript提示
  • 用unref统一处理
  • 用reactive避免.value

4. 坑四:reactive解构丢失响应式

第四个坑:reactive解构丢失响应式。

const state = reactive({ count: 0 })
const { count } = state  // 丢失响应式
  • 解构后就不是响应式了
  • 刚开始不知道
  • 后来学会了用toRefs

解决方法:

import { toRefs } from 'vue'
const { count } = toRefs(state)

Vue 3.3的响应式props解构,也是类似的原理。

三、响应式的坑

1. 坑一:直接替换reactive对象

第一个坑:直接替换reactive对象。

const state = reactive({ list: [] })
state.list = newList  // 可以
state = reactive({ list: newList })  // 不行,丢失响应式
  • reactive不能直接替换整个对象
  • 只能替换属性
  • 刚开始犯过这个错

解决方法:

  • 用ref包对象
  • 或者替换属性
  • 不要替换整个reactive对象

2. 坑二:数组的响应式

第二个坑:数组的响应式。

  • Vue 2中数组的某些方法不触发更新
  • Vue 3中用Proxy解决了
  • 但直接用索引修改还是有问题
  • 要用splice或数组方法

Vue 3的响应式,比Vue 2好多了,但也要注意。

3. 坑三:watch的坑

第三个坑:watch的坑。

  • watch监听ref
  • watch监听reactive
  • 深度监听
  • 立即执行
  • 刚开始参数搞不清

我的经验:

// 监听ref
watch(count, (newVal, oldVal) => {})

// 监听reactive的属性
watch(() => state.count, (newVal, oldVal) => {})

// 深度监听
watch(state, callback, { deep: true })

// 立即执行
watch(source, callback, { immediate: true })

4. 坑四:computed的坑

第四个坑:computed的坑。

  • computed默认是只读的
  • 要写可写的computed需要get和set
  • 刚开始不知道
  • 直接赋值报错

解决方法:

const fullName = computed({
    get: () => firstName.value + ' ' + lastName.value,
    set: (val) => {
        const [first, last] = val.split(' ')
        firstName.value = first
        lastName.value = last
    }
})

四、组件通信的坑

1. 坑一:props的坑

第一个坑:props的坑。

  • props是只读的
  • 不能直接修改
  • 要通过emit修改
  • 刚开始直接改props
  • 控制台报警告

解决方法:

  • 用computed包装
  • 用emit通知父组件
  • 用v-model
  • 不要直接改props

2. 坑二:defineProps和withDefaults

第二个坑:defineProps和withDefaults。

Vue 3.3中:

const props = defineProps<{
    name: string
    age?: number
}>()

const props = withDefaults(defineProps<{
    name: string
    age?: number
}>(), {
    age: 18
})
  • 刚开始不知道withDefaults
  • 不知道怎么设默认值
  • 后来学会了

3. 坑三:defineEmits

第三个坑:defineEmits。

Vue 3.3中更简洁:

const emit = defineEmits<{
    (e: 'update', value: string): void
    (e: 'close'): void
}>()
  • 类型声明方式
  • 刚开始不习惯
  • 后来觉得很好用

4. 坑四:provide/inject

第四个坑:provide/inject。

  • 跨层级通信
  • 但调试困难
  • 不知道数据从哪来
  • 容易出问题

我的建议:

  • 简单场景用provide/inject
  • 复杂场景用状态管理
  • 不要滥用
  • 保持数据流清晰

五、TypeScript的坑

1. 坑一:ref的类型

第一个坑:ref的类型。

const count = ref<number>(0)
const user = ref<User | null>(null)
  • ref要指定类型
  • 特别是可能为null的
  • 刚开始不指定
  • 后来发现类型推断有问题

2. 坑二:reactive的类型

第二个坑:reactive的类型。

interface State {
    count: number
    list: Item[]
}
const state = reactive<State>({
    count: 0,
    list: []
})
  • reactive最好指定接口
  • 类型更清晰
  • 代码更可维护

3. 坑三:组件的类型

第三个坑:组件的类型。

  • 组件的props类型
  • 组件的emits类型
  • 组件的实例类型
  • 刚开始搞不清

Vue 3.3改进了很多,类型推导更好了。

4. 坑四:模板中的类型

第四个坑:模板中的类型。

  • 模板中没有类型检查
  • 写错了不知道
  • 要用Volar插件
  • 开启模板类型检查

工具,能帮你避免很多坑。

六、构建部署的坑

1. 坑一:vite配置

第一个坑:vite配置。

  • Vue 3推荐用Vite
  • Vite配置和webpack不同
  • 刚开始不习惯
  • 别名、代理、环境变量
  • 都要重新学

Vite,是新的构建工具,需要学习。

2. 坑二:环境变量

第二个坑:环境变量。

  • Vite中环境变量要以VITE_开头
  • 用import.meta.env访问
  • 和webpack的process.env不同
  • 刚开始不知道

解决方法:

// .env
VITE_API_URL=https://api.example.com

// 代码中
const apiUrl = import.meta.env.VITE_API_URL

3. 坑三:打包优化

第三个坑:打包优化。

  • 代码分割
  • 懒加载
  • 预加载
  • 压缩
  • CDN

打包优化,是性能的关键。

4. 坑四:部署的坑

第四个坑:部署的坑。

  • 路由history模式的404问题
  • 静态资源路径
  • 跨域问题
  • 缓存问题

部署,也有很多坑。

七、性能优化的坑

1. 坑一:不必要的重渲染

第一个坑:不必要的重渲染。

  • 父组件渲染,子组件都渲染
  • 大列表性能差
  • 要用v-memo
  • 要用shallowRef
  • 要合理拆分组件

性能优化,是Vue开发的必修课。

2. 坑二:大列表

第二个坑:大列表。

  • 大量数据渲染慢
  • 要用虚拟列表
  • 要用分页
  • 不要一次渲染太多

大列表,是性能的杀手。

3. 坑三:computed滥用

第三个坑:computed滥用。

  • 什么都用computed
  • 其实有些用方法就行
  • computed有缓存
  • 但也有开销
  • 合理使用

computed,不是越多越好。

4. 坑四:watch滥用

第四个坑:watch滥用。

  • 什么都用watch
  • 其实可以用computed
  • 可以用v-model
  • 可以用事件
  • 不要滥用watch

watch,要谨慎使用。

八、我的经验总结

1. 仔细看文档

第一个经验:仔细看文档。

  • Vue文档写得很好
  • 新特性先看文档
  • 理解原理再用
  • 不要想当然
  • 文档会告诉你坑在哪里

文档,是最好的老师。

2. 用TypeScript

第二个经验:用TypeScript。

  • TypeScript能发现很多问题
  • 类型提示很方便
  • 重构更安全
  • 代码更可维护
  • 强烈推荐

TypeScript,是Vue 3的好搭档。

3. 用Volar

第三个经验:用Volar。

  • Volar是Vue 3的官方VS Code插件
  • 类型提示
  • 模板检查
  • 自动补全
  • 很好用

工具,能提高效率。

4. 组件拆分

第四个经验:合理拆分组件。

  • 组件不要太大
  • 职责单一
  • 可复用
  • 可测试
  • 好的组件结构

组件拆分,是好代码的基础。

5. 状态管理

第五个经验:合理的状态管理。

  • 简单场景用props/emit
  • 跨层级用provide/inject
  • 复杂场景用Pinia
  • 不要什么都用全局状态
  • 保持数据流清晰

状态管理,是架构的核心。

九、写在最后

Vue 3.3+实战,那些年我踩过的坑。

Composition API的坑:setup中没有this、ref和reactive分不清、ref需要.value、reactive解构丢失响应式。响应式的坑:直接替换reactive对象、数组的响应式、watch和computed的坑。组件通信的坑:props只读、defineProps和withDefaults、defineEmits、provide/inject。TypeScript的坑:ref和reactive的类型、组件类型、模板类型。构建部署的坑:vite配置、环境变量、打包优化、部署。性能优化的坑:不必要的重渲染、大列表、computed和watch滥用。

2023年了,Vue 3已经很成熟了,Vue 3.3带来了很多改进。但不管版本怎么变,核心的开发经验是不变的:理解原理、写好代码、注意性能、持续学习。踩坑不可怕,可怕的是不从坑中学习。

最后,用一句话总结:"Vue 3的坑,大多是因为不理解原理。理解了原理,就能避开大部分坑。仔细看文档,用TypeScript,用Volar,合理拆分组件,你也能写出好的Vue代码。"

希望我的经验,能帮你少踩一些坑。