从 Vue 2 到 Vue 3:一位前端工程师的实战学习笔记

从 Vue 2 到 Vue 3:一位前端工程师的实战学习笔记

前言

Vue 3 正式发布已经两年多了,很多团队已经完成了从 Vue 2 到 Vue 3 的迁移。作为一个在 Vue 2 上写了三年业务代码的前端工程师,我花了两个月系统学习了 Vue 3 的核心变化,边学边用,踩了不少坑,也收获了很多。

这篇文章是我个人的学习总结,不是官方文档的翻译,而是从实战角度出发梳理 Vue 3 的关键变化和最佳实践。希望能帮到正在学习 Vue 3 的朋友。

一、Composition API:不只是语法糖

为什么需要 Composition API?

Vue 2 的 Options API 在小项目中很好用,但随着组件复杂度上升,你会发现一个问题:同一个逻辑的代码被拆散在不同的 Options 里

比如一个搜索组件,data 里定义搜索关键词和结果列表,methods 里写搜索方法,watch 里监听输入变化,computed 里处理搜索结果的过滤。一个功能横跨四个配置项,组件大了之后非常难维护。

Composition API 允许你按功能组织代码,而不是按选项类型:

vue 复制代码
<script setup>
import { ref, computed, watch } from 'vue'

// 搜索功能 - 所有相关代码在一起
const searchQuery = ref('')
const searchResults = ref([])
const filteredResults = computed(() => 
  searchResults.value.filter(item => item.active)
)

async function doSearch() {
  searchResults.value = await fetch(`/api/search?q=${searchQuery.value}`)
}

watch(searchQuery, (val) => {
  if (val.length > 2) doSearch()
})
</script>

setup 函数 vs <script setup>

Vue 3.2 引入了 <script setup> 语法糖,它让 Composition API 的写法更简洁:

vue 复制代码
<!-- ❌ 旧写法:setup 函数需要 return -->
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    return { count }
  }
}
</script>

<!-- ✅ 新写法:<script setup> 自动暴露 -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

建议:所有新项目直接用 <script setup>,简洁高效。

ref 和 reactive 怎么选?

这是初学者最容易纠结的问题。我的实践建议:

  • ref :基础类型 + 需要重新赋值的引用类型(value = newValue
  • reactive:深层嵌套的对象,不需要整体替换
vue 复制代码
<script setup>
import { ref, reactive } from 'vue'

// ref - 基础类型
const count = ref(0)

// ref - 引用类型,需要整体替换时
const user = ref(null)
function updateUser(data) {
  user.value = data  // 整体替换
}

// reactive - 深层对象,不需要整体替换
const form = reactive({
  name: '',
  email: '',
  address: {
    city: '',
    street: ''
  }
})
function resetForm() {
  // reactive 不能直接 = {} 赋值
  Object.assign(form, { name: '', email: '', address: { city: '', street: '' } })
}
</script>

核心原则: 当你需要重新赋值时用 ref;当你需要深层响应式且不整体替换时用 reactive。更多情况下我用 ref,因为它更灵活。

二、响应式进阶

shallowRef 和 triggerRef

当你有一个大型不可变数据 (比如从 API 获取的列表),每次更新都触发完整的响应式依赖追踪会有性能开销。shallowRef 只追踪 .value 的变化,内部数据变化需要手动触发更新:

vue 复制代码
<script setup>
import { shallowRef, triggerRef } from 'vue'

const largeList = shallowRef([
  { id: 1, name: 'Item 1', selected: false },
  // ... 几百条数据
])

function toggleItem(id) {
  const item = largeList.value.find(i => i.id === id)
  if (item) {
    item.selected = !item.selected
    triggerRef(largeList)  // 手动触发更新
  }
}
</script>

computed 的最佳实践

computed 支持 getset,但不建议滥用 set。复杂逻辑用 methods + ref 更清晰:

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// ✅ 好:只读 computed
const fullName = computed(() => `${firstName.value}${lastName.value}`)

// ⚠️ 可接受:带 set 的 computed
const displayName = computed({
  get: () => `${firstName.value}${lastName.value}`,
  set: (val) => {
    [firstName.value, lastName.value] = val.split('')
  }
})

// ❌ 不好:setter 逻辑太复杂
// 不如直接用 methods
</script>

watch 和 watchEffect

watch 监听特定数据变化,watchEffect 自动追踪依赖:

vue 复制代码
<script setup>
import { ref, watch, watchEffect } from 'vue'

const keyword = ref('')
const page = ref(1)

// watch:监听特定源,需要旧值对比
watch(keyword, (newVal, oldVal) => {
  if (newVal !== oldVal) {
    page.value = 1  // 搜索词变了,重置页码
  }
})

// watchEffect:自动收集依赖,立即执行
watchEffect(() => {
  // 这里用到的所有 ref 都会被追踪
  console.log(`搜索:${keyword.value},第 ${page.value} 页`)
})
</script>

选择指南:

  • 需要旧值 → watch
  • 需要延迟执行({ immediate: false })→ watch
  • 不需要指定依赖,自动收集 → watchEffect

三、组件通信

defineProps 和 defineEmits

<script setup> 中无需 import,直接使用:

vue 复制代码
<!-- Child.vue -->
<script setup>
const props = defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 }
})

const emit = defineEmits(['update', 'delete'])

function handleClick() {
  emit('update', props.count + 1)
}
</script>

<template>
  <div @click="handleClick">
    {{ title }}: {{ count }}
  </div>
</template>

v-model 新语法

Vue 3 支持多个 v-model

vue 复制代码
<!-- Parent.vue -->
<Child 
  v-model:title="title" 
  v-model:content="content" 
/>

<!-- Child.vue -->
<script setup>
defineProps({ title: String, content: String })
defineEmits(['update:title', 'update:content'])
</script>

provide / inject 的响应式

跨层级通信时,最好传 ref 或 reactive,而不是直接传值:

vue 复制代码
<!-- 祖先组件 -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>

<!-- 子孙组件 -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
  <div :class="theme">
    <button @click="toggleTheme">切换主题</button>
  </div>
</template>

四、Teleport 和 Suspense

Teleport

适合弹窗、Toast、下拉菜单等需要挂载到 DOM 特定位置的情况:

vue 复制代码
<template>
  <button @click="showModal = true">打开弹窗</button>
  
  <Teleport to="body">
    <div v-if="showModal" class="modal-overlay">
      <div class="modal-content">
        <slot />
        <button @click="showModal = false">关闭</button>
      </div>
    </div>
  </Teleport>
</template>

Suspense(实验性)

虽然还没有正式稳定,但已经在很多项目中使用。适合处理异步组件:

vue 复制代码
<template>
  <Suspense>
    <template #default>
      <AsyncDashboard />
    </template>
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

五、TypeScript 集成

Vue 3 对 TypeScript 的支持是一等公民。推荐所有新项目使用 TypeScript:

vue 复制代码
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

const users = ref<User[]>([])
const userMap = reactive<Map<number, User>>(new Map())

async function fetchUsers() {
  users.value = await $fetch<User[]>('/api/users')
}

// 给 props 定义类型
const props = defineProps<{
  title: string
  items?: string[]
  onSelect?: (id: number) => void
}>()

// emit 类型化
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'delete', ids: number[]): void
}>()
</script>

六、性能优化实战

1. 合理使用 v-memo

v-memo 可以避免不必要的虚拟 DOM 比较,适合大型列表:

vue 复制代码
<template>
  <div v-for="item in list" :key="item.id" v-memo="[item.id, item.updatedAt]">
    {{ item.content }}
  </div>
</template>

2. 函数式组件

对于纯展示组件,使用函数式组件减少开销:

vue 复制代码
<template functional>
  <div class="badge" :class="`badge--${type}`">
    <slot />
  </div>
</template>

3. 使用 defineAsyncComponent 做代码分割

vue 复制代码
<script setup>
import { defineAsyncComponent } from 'vue'

const HeavyComponent = defineAsyncComponent(() => 
  import('./HeavyComponent.vue')
)
</script>

<template>
  <HeavyComponent />
</template>

七、常见迁移踩坑

1. v-model 默认值的变更

Vue 3 中 v-model 默认使用 modelValueupdate:modelValue,Vue 2 的 .sync 修饰符被合并到 v-model

2. filters 被移除

Vue 3 不再支持 filters,改用 computed 或 methods:

vue 复制代码
// ❌ Vue 2
{{ price | formatCurrency }}

// ✅ Vue 3
{{ formatCurrency(price) }}

3. $listeners 被移除

Vue 3 中 $attrs 包含了属性和监听器:

vue 复制代码
// ❌ Vue 2
v-bind="$attrs" v-on="$listeners"

// ✅ Vue 3
v-bind="$attrs"

4. 自定义指令生命周期变化

指令钩子名称和组件生命周期对齐:

js 复制代码
// Vue 2
directives: {
  focus: { inserted(el) { el.focus() } }
}

// Vue 3
directives: {
  focus: { mounted(el) { el.focus() } }
}

八、项目推荐实践

基于我这几个月的实践,推荐以下技术栈组合:

dart 复制代码
Vue 3 + Vite + TypeScript
├── 路由 → Vue Router 4
├── 状态管理 → Pinia(替代 Vuex)
├── UI 组件 → Element Plus / Naive UI
├── 请求 → Axios + Vue Request
├── 测试 → Vitest + Vue Test Utils
└── 构建 → Vite + @vitejs/plugin-vue

Pinia 比起 Vuex 的优势:

  • 完整的 TypeScript 支持,无需额外类型包装
  • 没有 mutations,只有 actions,少一层概念
  • 支持 Composition API 风格的 store
  • 更轻量(~1KB)

总结

Vue 3 的学习曲线确实比 Vue 2 陡一些,主要是 Composition API 和 <script setup> 的思维方式需要适应。但一旦上手,你会发现代码的组织性和可维护性提升了一个档次。

对于正在学习的朋友,我的建议是:

  1. 先学 Composition API,这是 Vue 3 的核心
  2. <script setup> 写组件,生产级体验
  3. 配上 TypeScript,Vue 3 的 TS 支持真的很好
  4. 迁移时不要贪快,一个组件一个组件改
  5. 多读优秀开源项目的源码,比如 Naive UI、Element Plus

希望这篇文章对你的 Vue 3 学习之路有帮助。如果有任何问题,欢迎在评论区交流!

相关推荐
3D探路人2 小时前
模灵 大模型聚合API 转发流程技术实现
java·大数据·开发语言·前端·人工智能·计算机视觉
程似锦吖2 小时前
无中生有 之 从0开始写一个动态定时任务管理
java·开发语言
techdashen2 小时前
dial9:给 Tokio 装上“飞行记录仪“
java·数据库·redis
ShiJiuD6668889993 小时前
springboot基础篇
java·spring boot·spring
砚底藏山河3 小时前
python、JavaScript 、JAVA,定制化数据服务,助力业务高效落地
java·javascript·python
qq_452396233 小时前
第六篇:《JMeter逻辑控制器:循环、条件和交替执行》
android·java·jmeter
humcomm3 小时前
Java 新特性2026年5月速览
java·开发语言
luck_bor3 小时前
集合进阶(Collections Set List)
java
敲敲千反田3 小时前
Spring AI
java·人工智能·spring