Vue3全面解析 - 从入门到精通

发布于:2025-07-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

1. Vue3简介与新特性

1.1 Vue3的革命性变化

1.2 Vue3的核心新特性

1.3 主要改进点

2. 环境搭建与项目创建

2.1 使用Vite创建项目(推荐)

2.2 项目结构解析

2.3 入口文件对比

2.4 开发服务器启动

3. Composition API核心概念

3.1 为什么需要Composition API?

3.2 setup函数基础

3.3 script setup语法糖

4. 响应式系统详解

4.1 Vue3响应式原理

4.2 reactive() - 深度响应式

4.3 ref() - 基本类型响应式

4.4 toRef() 和 toRefs()

4.5 readonly() - 只读响应式

4.6 shallowRef() 和 shallowReactive()

5. setup函数深入解析

5.1 setup函数的执行时机

5.2 setup参数详解

5.3 与Options API混用

5.4 setup中的this和生命周期

5.5 异步setup

6. ref和reactive详解

6.1 ref的深入理解

6.2 reactive的深入理解

6.3 ref vs reactive选择指南

6.4 响应式工具函数

6.5 响应式最佳实践

7. computed和watch新用法

7.1 computed组合式API

7.2 watch深入解析

7.3 watchEffect的强大功能

7.4 watch vs watchEffect

7.5 高级监听模式

8. 生命周期组合式API

8.1 生命周期对比

8.2 生命周期实际应用

8.3 多个生命周期钩子

8.4 生命周期在自定义Hook中的使用

8.5 生命周期最佳实践

9. 模板语法与指令更新

9.1 Vue3模板语法改进

9.2 v-model的增强

9.3 自定义v-model修饰符

9.4 新的指令特性

9.5 模板引用的新语法

9.6 插槽的增强语法

10. 组件系统升级

10.1 defineComponent函数

10.2 组件Props的增强定义

10.3 Emits的完整定义

10.4 expose暴露组件方法

10.5 异步组件的新语法

10.6 函数式组件

10.7 组件v-model的完整实现

11. Teleport传送门

11.1 Teleport基础概念

11.2 Teleport的高级用法

11.3 通用Modal组件

11.4 通知组件

11.5 Teleport的实际应用场景

12. Suspense异步组件

12.1 Suspense基础概念

12.2 异步组件的创建

12.3 嵌套Suspense

12.4 错误处理

12.5 高级Suspense模式

12.6 实用的Suspense Hooks

13. Fragment多根节点

13.1 Fragment基础概念

13.2 Fragment的实际应用

13.3 条件Fragment

13.4 Fragment在循环中的使用

13.5 Fragment的注意事项

14. 自定义Hooks

14.1 自定义Hooks概念

14.2 状态管理Hooks

14.3 HTTP请求Hooks

14.4 表单处理Hooks

14.5 UI交互Hooks

14.6 组合Hooks实例

15. TypeScript支持

15.1 Vue3的TypeScript配置

15.2 组件类型定义

15.3 Composition API的类型

15.4 泛型Hook

15.5 全局类型定义

15.6 路由类型定义

15.7 Store类型定义

15.8 组件测试类型

16. 全局API变更

16.1 应用实例API

16.2 配置对象变更

16.3 全局API移除和替代

16.4 新的全局API

16.5 Tree-shaking友好的API

16.6 插件系统变更

16.7 多应用实例

17. 性能优化与最佳实践

17.1 编译时优化

17.2 响应式性能优化

17.3 组件性能优化

17.4 虚拟滚动优化

17.5 代码分割和懒加载

17.6 Bundle优化

17.7 内存管理

17.8 开发工具和调试

18. Vue Router 4.x

18.1 Vue Router 4基础配置

18.2 组合式API中使用路由

18.3 路由守卫

18.4 动态路由

18.5 路由过渡动画

18.6 路由懒加载和预加载

18.7 自定义路由Hook

19. Pinia状态管理

19.1 Pinia基础配置

19.2 定义Store

19.3 在组件中使用Store

19.4 Store组合和模块化

19.5 插件和持久化

19.6 DevTools和调试

19.7 测试Store

20. 从Vue2迁移到Vue3

20.1 迁移策略

20.2 主要API变更

20.3 组件迁移指南

20.4 路由迁移

20.5 状态管理迁移

20.6 工具链迁移

20.7 迁移检查清单

20.8 常见迁移问题和解决方案

总结

🚀 主要优势

🎯 核心概念掌握

💡 最佳实践

12.3 嵌套Suspense

12.4 错误处理

12.5 高级Suspense模式


1. Vue3简介与新特性

1.1 Vue3的革命性变化

Vue3是Vue.js的下一个主要版本,它带来了许多激动人心的新特性和改进。最重要的是,Vue3完全向后兼容Vue2的绝大部分功能,同时提供了更强大、更灵活的开发体验。

为什么要学习Vue3?

  • 更好的性能:Bundle体积减少41%,初次渲染快55%,更新渲染快133%
  • 更好的Tree-shaking:未使用的功能不会被打包
  • Composition API:更好的逻辑复用和代码组织
  • 更好的TypeScript支持:原生TypeScript支持
  • 新的特性:Teleport、Suspense、Fragment等

1.2 Vue3的核心新特性

// Vue2的写法
export default {
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

// Vue3 Composition API的写法
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      message,
      doubleCount,
      increment
    }
  }
}

关键差异解释:

  • Vue2使用Options API(选项式API),把数据、方法、计算属性分开写
  • Vue3引入了Composition API(组合式API),可以把相关的逻辑组织在一起
  • ref()创建响应式数据,通过.value访问
  • computed()创建计算属性
  • setup()是组合式API的入口函数

1.3 主要改进点

1. 性能提升

// Vue3的响应式系统使用Proxy,性能更好
const state = reactive({
  name: 'Vue3',
  version: '3.0'
})

// 直接修改就能响应,不需要Vue.set
state.newProperty = 'new value' // ✅ 响应式的
delete state.version // ✅ 响应式的

2. 更好的开发体验

<!-- Vue3支持多个根元素 -->
<template>
  <header>头部</header>
  <main>主要内容</main>
  <footer>底部</footer>
</template>

3. 更小的包体积

// Vue3支持按需引入
import { createApp, ref, computed } from 'vue'
// 只引入需要的功能,减少包体积

2. 环境搭建与项目创建

2.1 使用Vite创建项目(推荐)

Vite是Vue3官方推荐的构建工具,启动速度极快,热更新体验优秀。

# 使用npm
npm create vue@latest my-vue3-project

# 使用yarn
yarn create vue my-vue3-project

# 使用pnpm
pnpm create vue my-vue3-project

创建过程中会询问项目配置:

✔ Project name: … my-vue3-project
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

2.2 项目结构解析

my-vue3-project/
├── public/                 # 静态资源目录
│   └── favicon.ico
├── src/                    # 源码目录
│   ├── assets/            # 资源文件
│   ├── components/        # 组件目录
│   ├── views/             # 页面组件
│   ├── router/            # 路由配置
│   ├── stores/            # Pinia状态管理
│   ├── App.vue            # 根组件
│   └── main.js            # 入口文件
├── index.html             # HTML模板
├── vite.config.js         # Vite配置
└── package.json           # 项目配置

2.3 入口文件对比

Vue2的main.js

import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

Vue3的main.js

import { createApp } from 'vue'
import App from './App.vue'

// 创建应用实例
const app = createApp(App)

// 挂载到DOM
app.mount('#app')

// 可以链式调用
createApp(App)
  .use(router)
  .use(store)
  .mount('#app')

重要变化说明:

  • Vue3不再是构造函数,而是通过createApp创建应用实例
  • 应用实例与全局Vue分离,避免了全局污染
  • 支持多个应用实例同时存在

2.4 开发服务器启动

cd my-vue3-project
npm install
npm run dev

Vite的开发服务器通常在几秒内就能启动,相比Webpack要快很多。


3. Composition API核心概念

3.1 为什么需要Composition API?

Options API的局限性:

// Vue2: 相关逻辑分散在不同选项中
export default {
  data() {
    return {
      // 用户相关数据
      users: [],
      currentUser: null,
      // 产品相关数据
      products: [],
      currentProduct: null
    }
  },
  computed: {
    // 用户相关计算属性
    userCount() {
      return this.users.length
    },
    // 产品相关计算属性
    productCount() {
      return this.products.length
    }
  },
  methods: {
    // 用户相关方法
    fetchUsers() { /* ... */ },
    updateUser() { /* ... */ },
    // 产品相关方法
    fetchProducts() { /* ... */ },
    updateProduct() { /* ... */ }
  }
}

当组件变得复杂时,相关的逻辑被分散在不同的选项中,难以维护。

Composition API的优势:

// Vue3: 相关逻辑可以组织在一起
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // 用户相关逻辑
    const useUsers = () => {
      const users = ref([])
      const currentUser = ref(null)
      
      const userCount = computed(() => users.value.length)
      
      const fetchUsers = async () => {
        // 获取用户逻辑
      }
      
      const updateUser = (user) => {
        // 更新用户逻辑
      }
      
      onMounted(fetchUsers)
      
      return {
        users,
        currentUser,
        userCount,
        fetchUsers,
        updateUser
      }
    }
    
    // 产品相关逻辑
    const useProducts = () => {
      const products = ref([])
      const currentProduct = ref(null)
      
      const productCount = computed(() => products.value.length)
      
      const fetchProducts = async () => {
        // 获取产品逻辑
      }
      
      return {
        products,
        currentProduct,
        productCount,
        fetchProducts
      }
    }
    
    // 组合使用
    const userState = useUsers()
    const productState = useProducts()
    
    return {
      ...userState,
      ...productState
    }
  }
}

3.2 setup函数基础

setup函数是Composition API的入口点,它在组件创建之前执行。

export default {
  // Props定义
  props: {
    title: String,
    count: Number
  },
  
  // setup函数
  setup(props, context) {
    console.log('Props:', props) // 响应式的props对象
    console.log('Context:', context) // 包含attrs, slots, emit, expose
    
    // 在这里定义响应式数据
    const message = ref('Hello Vue3')
    
    // 定义方法
    const updateMessage = () => {
      message.value = 'Updated!'
    }
    
    // 返回模板需要的数据和方法
    return {
      message,
      updateMessage
    }
  }
}

setup函数的参数:

  1. props: 组件接收的props(响应式)
  2. context: 包含四个属性的对象
    • attrs: 非响应式的属性对象
    • slots: 插槽对象
    • emit: 触发事件的函数
    • expose: 暴露公共属性的函数

3.3 script setup语法糖

Vue3.2引入的<script setup>语法糖让代码更简洁:

<!-- 传统setup写法 -->
<script>
import { ref, computed } from 'vue'

export default {
  props: {
    title: String
  },
  emits: ['update'],
  setup(props, { emit }) {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
      emit('update', count.value)
    }
    
    return {
      count,
      doubleCount,
      increment
    }
  }
}
</script>
<!-- script setup语法糖 -->
<script setup>
import { ref, computed } from 'vue'

// 定义props
const props = defineProps({
  title: String
})

// 定义emits
const emit = defineEmits(['update'])

// 响应式数据
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

// 方法
const increment = () => {
  count.value++
  emit('update', count.value)
}

// 不需要return,所有顶层变量都会暴露给模板
</script>

script setup的优势:

  • 代码更简洁,减少样板代码
  • 更好的性能(编译时优化)
  • 更好的TypeScript推导
  • 自动暴露顶层变量

4. 响应式系统详解

4.1 Vue3响应式原理

Vue3使用ES6的Proxy重写了响应式系统,相比Vue2的Object.defineProperty有很大优势。

// Vue2的响应式(Object.defineProperty)
// 只能监听已存在的属性,无法监听数组索引和新增属性

// Vue3的响应式(Proxy)
const target = { name: 'Vue3', version: '3.0' }
const proxy = new Proxy(target, {
  get(target, key) {
    console.log(`获取 ${key}: ${target[key]}`)
    return target[key]
  },
  set(target, key, value) {
    console.log(`设置 ${key}: ${value}`)
    target[key] = value
    return true
  },
  deleteProperty(target, key) {
    console.log(`删除 ${key}`)
    delete target[key]
    return true
  }
})

// 可以监听所有操作
proxy.name // 获取 name: Vue3
proxy.newProp = 'new' // 设置 newProp: new
delete proxy.version // 删除 version

4.2 reactive() - 深度响应式

reactive()用于创建深度响应式的对象:

import { reactive } from 'vue'

export default {
  setup() {
    // 创建响应式对象
    const state = reactive({
      count: 0,
      user: {
        name: '张三',
        profile: {
          age: 25,
          city: '北京'
        }
      },
      todos: [
        { id: 1, text: '学习Vue3', done: false }
      ]
    })
    
    // 所有嵌套属性都是响应式的
    const updateUser = () => {
      state.user.name = '李四' // ✅ 响应式
      state.user.profile.age = 26 // ✅ 响应式
      state.todos[0].done = true // ✅ 响应式
      
      // 直接添加新属性也是响应式的
      state.user.email = 'lisi@example.com' // ✅ 响应式
      
      // 数组操作都是响应式的
      state.todos.push({ id: 2, text: '学习Composition API', done: false }) // ✅ 响应式
    }
    
    return {
      state,
      updateUser
    }
  }
}

reactive的特点:

  • 只能用于对象类型(对象、数组、Map、Set等)
  • 返回的是Proxy对象,不等于原始对象
  • 深度响应式,所有嵌套属性都会变成响应式
  • 不能被解构,解构后会失去响应式
// ❌ 错误:解构会失去响应式
const { count } = reactive({ count: 0 })
count++ // 不会触发更新

// ✅ 正确:保持对象结构
const state = reactive({ count: 0 })
state.count++ // 会触发更新

4.3 ref() - 基本类型响应式

ref()用于创建任何类型的响应式数据:

import { ref } from 'vue'

export default {
  setup() {
    // 基本类型
    const count = ref(0)
    const message = ref('Hello')
    const isVisible = ref(true)
    
    // 对象类型(会自动转换为reactive)
    const user = ref({
      name: '张三',
      age: 25
    })
    
    // 数组
    const items = ref(['apple', 'banana'])
    
    // 修改值需要通过.value
    const updateData = () => {
      count.value++
      message.value = 'Hi'
      isVisible.value = false
      
      // 对象类型可以直接修改属性
      user.value.name = '李四'
      // 或者替换整个对象
      user.value = { name: '王五', age: 30 }
      
      // 数组操作
      items.value.push('orange')
    }
    
    return {
      count,
      message,
      isVisible,
      user,
      items,
      updateData
    }
  }
}

ref的特点:

  • 可以用于任何类型的数据
  • 通过.value访问和修改值
  • 在模板中会自动解包,不需要.value
  • 对象类型会自动调用reactive()
<template>
  <!-- 模板中自动解包,不需要.value -->
  <div>{
  { count }}</div>
  <div>{
  { message }}</div>
  <div>{
  { user.name }}</div>
</template>

4.4 toRef() 和 toRefs()

用于将响应式对象的属性转换为ref:

import { reactive, toRef, toRefs } from 'vue'

export default {
  setup() {
    const state = reactive({
      name: '张三',
      age: 25,
      city: '北京'
    })
    
    // toRef: 转换单个属性
    const name = toRef(state, 'name')
    console.log(name.value) // '张三'
    name.value = '李四' // 会同步更新state.name
    
    // toRefs: 转换所有属性
    const stateRefs = toRefs(state)
    // 现在可以解构了
    const { age, city } = stateRefs
    
    return {
      name,
      age,
      city,
      // 或者直接解构
      ...toRefs(state)
    }
  }
}

使用场景:

// 在自定义Hook中返回响应式数据
function useCounter() {
  const state = reactive({
    count: 0,
    step: 1
  })
  
  const increment = () => {
    state.count += state.step
  }
  
  // 返回时使用toRefs,让组件可以解构使用
  return {
    ...toRefs(state),
    increment
  }
}

// 在组件中使用
export default {
  setup() {
    // 可以直接解构,不会失去响应式
    const { count, step, increment } = useCounter()
    
    return {
      count,
      step,
      increment
    }
  }
}

4.5 readonly() - 只读响应式

创建只读的响应式数据:

import { reactive, readonly } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      user: { name: '张三' }
    })
    
    // 创建只读版本
    const readonlyState = readonly(state)
    
    // ❌ 在开发模式下会警告
    readonlyState.count = 1
    readonlyState.user.name = '李四'
    
    // ✅ 原始数据仍然可以修改
    state.count = 1
    
    return {
      state,
      readonlyState
    }
  }
}

4.6 shallowRef() 和 shallowReactive()

创建浅层响应式数据:

import { shallowRef, shallowReactive } from 'vue'

export default {
  setup() {
    // shallowRef: 只有.value的赋值是响应式的
    const state = shallowRef({
      count: 0,
      user: { name: '张三' }
    })
    
    // ✅ 响应式
    state.value = { count: 1, user: { name: '李四' } }
    
    // ❌ 不响应式
    state.value.count = 2
    state.value.user.name = '王五'
    
    // shallowReactive: 只有根级别属性是响应式的
    const shallowState = shallowReactive({
      count: 0,
      user: { name: '张三' }
    })
    
    // ✅ 响应式
    shallowState.count = 1
    
    // ❌ 不响应式
    shallowState.user.name = '李四'
    
    return {
      state,
      shallowState
    }
  }
}

性能优化场景:

  • 大型数据结构,只需要监听根级别变化
  • 与第三方库集成,避免深度响应式的性能开销

5. setup函数深入解析

5.1 setup函数的执行时机

export default {
  beforeCreate() {
    console.log('1. beforeCreate')
  },
  
  // setup在beforeCreate之前执行
  setup() {
    console.log('0. setup执行')
    
    // 此时还无法访问this
    console.log(this) // undefined
    
    return {}
  },
  
  created() {
    console.log('2. created')
  }
}

重要提醒:

  • setup执行时组件实例还未创建,所以无法访问this
  • setup中不能使用其他选项式API(如data、computed、methods等)
  • 其他选项式API可以访问setup返回的数据

5.2 setup参数详解

export default {
  props: {
    title: String,
    count: {
      type: Number,
      default: 0
    }
  },
  emits: ['update', 'delete'],
  
  setup(props, context) {
    // 第一个参数:props(响应式的)
    console.log('Props:', props)
    console.log('Title:', props.title)
    
    // props是响应式的,可以监听变化
    watch(() => props.count, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    })
    
    // ❌ 不要解构props,会失去响应式
    // const { title, count } = props
    
    // ✅ 如果需要解构,使用toRefs
    const { title, count } = toRefs(props)
    
    // 第二个参数:context
    const { attrs, slots, emit, expose } = context
    
    // attrs: 非props的属性
    console.log('Attrs:', attrs)
    
    // slots: 插槽
    console.log('Slots:', slots)
    
    // emit: 触发事件
    const handleUpdate = () => {
      emit('update', 'new value')
    }
    
    // expose: 暴露组件方法给父组件
    const internalMethod = () => {
      console.log('Internal method')
    }
    
    // 暴露给父组件使用
    expose({
      internalMethod
    })
    
    return {
      handleUpdate
    }
  }
}

5.3 与Options API混用

Vue3完全兼容Vue2的Options API,可以混合使用:

export default {
  // Options API
  data() {
    return {
      optionsCount: 0
    }
  },
  
  computed: {
    optionsDouble() {
      return this.optionsCount * 2
    }
  },
  
  // Composition API
  setup() {
    const compositionCount = ref(0)
    const compositionDouble = computed(() => compositionCount.value * 2)
    
    return {
      compositionCount,
      compositionDouble
    }
  },
  
  methods: {
    // 可以访问setup返回的数据
    logData() {
      console.log('Options count:', this.optionsCount)
      console.log('Composition count:', this.compositionCount)
    }
  }
}

混用规则:

  • setup返回的数据可以在Options API中通过this访问
  • Options API中的数据无法在setup中访问
  • 避免在同一个组件中混用,选择一种风格坚持使用

5.4 setup中的this和生命周期

export default {
  setup() {
    // ❌ setup中没有this
    console.log(this) // undefined
    
    // ✅ 使用组合式生命周期
    onMounted(() => {
      console.log('Component mounted')
    })
    
    onUpdated(() => {
      console.log('Component updated')
    })
    
    onUnmounted(() => {
      console.log('Component unmounted')
    })
    
    return {}
  }
}

5.5 异步setup

注意:setup不能是async函数

export default {
  // ❌ 错误:setup不能是async
  async setup() {
    const data = await fetchData()
    return { data }
  },
  
  // ✅ 正确:在setup内部处理异步
  setup() {
    const data = ref(null)
    const loading = ref(true)
    
    const fetchData = async () => {
      try {
        loading.value = true
        const response = await api.getData()
        data.value = response.data
      } catch (error) {
        console.error('Failed to fetch data:', error)
      } finally {
        loading.value = false
      }
    }
    
    // 组件挂载时获取数据
    onMounted(fetchData)
    
    return {
      data,
      loading,
      fetchData
    }
  }
}

如果确实需要异步setup,可以配合Suspense使用:

<!-- 父组件 -->
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>
// AsyncComponent.vue
export default {
  async setup() {
    // 这里可以使用async,但需要配合Suspense
    const data = await fetchData()
    return { data }
  }
}

6. ref和reactive详解

6.1 ref的深入理解

ref是Vue3中创建响应式数据的基础函数:

import { ref, isRef, unref } from 'vue'

export default {
  setup() {
    // 基本类型
    const count = ref(0)
    const message = ref('hello')
    const isVisible = ref(true)
    
    // 复杂类型
    const user = ref({
      name: '张三',
      age: 25,
      hobbies: ['读书', '游泳']
    })
    
    // 判断是否为ref
    console.log(isRef(count)) // true
    console.log(isRef(message)) // true
    console.log(isRef(123)) // false
    
    // 获取ref的值(如果是ref返回.value,否则返回原值)
    console.log(unref(count)) // 0
    console.log(unref(123)) // 123
    
    // ref的自动解包
    const nestedRef = ref({
      count: ref(1), // 嵌套的ref会自动解包
      user: ref({ name: '李四' })
    })
    
    console.log(nestedRef.value.count) // 1,不需要.value.count.value
    
    return {
      count,
      message,
      isVisible,
      user,
      nestedRef
    }
  }
}

ref的自动解包规则:

<template>
  <!-- 在模板中自动解包 -->
  <div>{
  { count }}</div> <!-- 不需要count.value -->
  
  <!-- 在响应式对象中自动解包 -->
  <div>{
  { state.count }}</div> <!-- 如果state.count是ref,会自动解包 -->
</template>

<script>
export default {
  setup() {
    const count = ref(0)
    
    // 在JavaScript中需要.value
    console.log(count.value)
    
    // 在响应式对象中会自动解包
    const state = reactive({
      count: count // 这里count会自动解包
    })
    
    console.log(state.count) // 0,不需要state.count.value
    
    return {
      count,
      state
    }
  }
}
</script>

6.2 reactive的深入理解

reactive用于创建深度响应式对象:

import { reactive, isReactive, toRaw, markRaw } from 'vue'

export default {
  setup() {
    // 创建响应式对象
    const state = reactive({
      count: 0,
      user: {
        name: '张三',
        profile: {
          age: 25,
          city: '北京'
        }
      },
      items: [
        { id: 1, name: 'item1' },
        { id: 2, name: 'item2' }
      ],
      map: new Map([
        ['key1', 'value1'],
        ['key2', 'value2']
      ]),
      set: new Set([1, 2, 3])
    })
    
    // 判断是否为响应式对象
    console.log(isReactive(state)) // true
    console.log(isReactive(state.user)) // true(嵌套对象也是响应式的)
    console.log(isReactive(state.items)) // true
    
    // 获取原始对象
    const raw = toRaw(state)
    console.log(raw === state) // false,state是代理对象
    
    // 标记对象为非响应式
    const nonReactiveObj = markRaw({
      expensiveData: new Array(10000).fill(0)
    })
    
    state.nonReactive = nonReactiveObj
    console.log(isReactive(state.nonReactive)) // false
    
    // 操作响应式数据
    const updateData = () => {
      // 对象属性
      state.count++
      state.user.name = '李四'
      state.user.profile.age = 26
      
      // 数组操作
      state.items.push({ id: 3, name: 'item3' })
      state.items[0].name = 'updated item1'
      
      // Map操作
      state.map.set('key3', 'value3')
      state.map.delete('key1')
      
      // Set操作
      state.set.add(4)
      state.set.delete(1)
      
      // 动态添加属性
      state.newProperty = 'new value' // Vue3中这是响应式的
    }
    
    return {
      state,
      updateData
    }
  }
}

6.3 ref vs reactive选择指南

export default {
  setup() {
    // ✅ 使用ref的场景
    
    // 1. 基本类型数据
    const count = ref(0)
    const message = ref('')
    const isLoading = ref(false)
    
    // 2. 需要替换整个对象的引用
    const user = ref({ name: '张三' })
    // 可以完全替换
    user.value = { name: '李四', age: 25 }
    
    // 3. 需要在模板中传递给组件
    const currentUser = ref(null)
    // <UserComponent :user="currentUser" />
    
    // ✅ 使用reactive的场景
    
    // 1. 复杂的对象结构,不需要替换引用
    const form = reactive({
      username: '',
      password: '',
      email: '',
      profile: {
        age: null,
        city: ''
      }
    })
    
    // 2. 本地组件状态
    const uiState = reactive({
      isMenuOpen: false,
      selectedTab: 'home',
      modals: {
        user: false,
        settings: false
      }
    })
    
    // ❌ 避免的用法
    
    // 1. 不要解构reactive
    // const { isMenuOpen, selectedTab } = uiState // 失去响应式
    
    // 2. 不要将reactive对象赋值给ref
    // const state = ref(reactive({ count: 0 })) // 不推荐
    
    return {
      count,
      message,
      isLoading,
      user,
      currentUser,
      form,
      uiState
    }
  }
}

选择原则:

  • 基本类型:使用ref
  • 对象类型且需要替换引用:使用ref
  • 对象类型且不需要替换引用:使用reactive
  • 需要解构的对象:使用ref或配合toRefs

6.4 响应式工具函数

import { 
  ref, 
  reactive, 
  isRef, 
  isReactive, 
  isReadonly, 
  isProxy,
  toRaw,
  markRaw,
  shallowRef,
  shallowReactive,
  readonly,
  shallowReadonly
} from 'vue'

export default {
  setup() {
    const refValue = ref(0)
    const reactiveValue = reactive({ count: 0 })
    const readonlyValue = readonly(reactiveValue)
    
    // 类型检查
    console.log('isRef:', isRef(refValue)) // true
    console.log('isReactive:', isReactive(reactiveValue)) // true
    console.log('isReadonly:', isReadonly(readonlyValue)) // true
    console.log('isProxy:', isProxy(reactiveValue)) // true
    
    // 获取原始值
    console.log('toRaw:', toRaw(reactiveValue))
    
    // 创建非响应式对象
    const rawObj = markRaw({ 
      heavyData: new Array(1000000).fill(0) 
    })
    
    // 浅层响应式
    const shallowState = shallowReactive({
      count: 0,
      deep: { value: 1 }
    })
    
    // 浅层只读
    const shallowReadonlyState = shallowReadonly({
      count: 0,
      deep: { value: 1 }
    })
    
    return {
      refValue,
      reactiveValue,
      readonlyValue,
      rawObj,
      shallowState,
      shallowReadonlyState
    }
  }
}

6.5 响应式最佳实践

// 自定义Hook示例
function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count: readonly(count), // 只读,防止外部直接修改
    increment,
    decrement,
    reset
  }
}

// 复杂状态管理
function useFormState() {
  const form = reactive({
    data: {
      username: '',
      email: '',
      password: ''
    },
    errors: {},
    touched: {},
    isSubmitting: false
  })
  
  const setField = (field, value) => {
    form.data[field] = value
    // 清除该字段的错误
    if (form.errors[field]) {
      delete form.errors[field]
    }
  }
  
  const setError = (field, message) => {
    form.errors[field] = message
  }
  
  const setTouched = (field) => {
    form.touched[field] = true
  }
  
  const isFieldValid = (field) => {
    return !form.errors[field] && form.touched[field]
  }
  
  const isFormValid = computed(() => {
    return Object.keys(form.errors).length === 0 &&
           Object.keys(form.touched).length > 0
  })
  
  return {
    form: readonly(form),
    setField,
    setError,
    setTouched,
    isFieldValid,
    isFormValid
  }
}

export default {
  setup() {
    const { count, increment, decrement, reset } = useCounter(10)
    const { form, setField, setError, isFormValid } = useFormState()
    
    return {
      count,
      increment,
      decrement,
      reset,
      form,
      setField,
      setError,
      isFormValid
    }
  }
}

7. computed和watch新用法

7.1 computed组合式API

Vue3的computed更加灵活和强大:

import { ref, computed, readonly } from 'vue'

export default {
  setup() {
    const firstName = ref('张')
    const lastName = ref('三')
    
    // 基础计算属性(只读)
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // 可写计算属性
    const fullNameWritable = computed({
      get() {
        return `${firstName.value} ${lastName.value}`
      },
      set(value) {
        const names = value.split(' ')
        firstName.value = names[0] || ''
        lastName.value = names[1] || ''
      }
    })
    
    // 复杂计算属性
    const users = ref([
      { id: 1, name: '张三', age: 25, active: true },
      { id: 2, name: '李四', age: 30, active: false },
      { id: 3, name: '王五', age: 35, active: true }
    ])
    
    const activeUsers = computed(() => {
      return users.value.filter(user => user.active)
    })
    
    const userStats = computed(() => {
      const total = users.value.length
      const active = activeUsers.value.length
      const averageAge = users.value.reduce((sum, user) => sum + user.age, 0) / total
      
      return {
        total,
        active,
        inactive: total - active,
        averageAge: Math.round(averageAge)
      }
    })
    
    // 计算属性的依赖是响应式的
    const expensiveValue = computed(() => {
      console.log('计算 expensiveValue') // 只有依赖变化时才会重新计算
      return users.value.map(user => ({
        ...user,
        displayName: `${user.name} (${user.age}岁)`
      }))
    })
    
    return {
      firstName,
      lastName,
      fullName,
      fullNameWritable,
      users,
      activeUsers,
      userStats,
      expensiveValue
    }
  }
}

7.2 watch深入解析

Vue3的watch提供了更多的选项和更强的功能:

import { ref, reactive, watch, watchEffect, nextTick } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('hello')
    const user = reactive({
      name: '张三',
      profile: {
        age: 25,
        city: '北京'
      }
    })
    
    // 1. 监听单个ref
    watch(count, (newValue, oldValue) => {
      console.log(`count changed: ${oldValue} -> ${newValue}`)
    })
    
    // 2. 监听多个源
    watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
      console.log('Multiple values changed:')
      console.log(`count: ${oldCount} -> ${newCount}`)
      console.log(`message: ${oldMessage} -> ${newMessage}`)
    })
    
    // 3. 监听响应式对象
    watch(user, (newUser, oldUser) => {
      console.log('User object changed:', newUser)
    }, {
      deep: true // 深度监听
    })
    
    // 4. 监听对象的特定属性
    watch(() => user.name, (newName, oldName) => {
      console.log(`User name changed: ${oldName} -> ${newName}`)
    })
    
    // 5. 监听嵌套属性
    watch(() => user.profile.age, (newAge, oldAge) => {
      console.log(`User age changed: ${oldAge} -> ${newAge}`)
    })
    
    // 6. 立即执行的监听器
    watch(count, (newValue) => {
      console.log('Count immediate:', newValue)
    }, {
      immediate: true // 立即执行一次
    })
    
    // 7. 回调的触发时机
    watch(count, async (newValue) => {
      console.log('Before DOM update:', newValue)
      await nextTick()
      console.log('After DOM update:', newValue)
    }, {
      flush: 'pre' // 'pre' | 'post' | 'sync'
    })
    
    // 8. 停止监听
    const stopWatcher = watch(message, (newValue) => {
      console.log('Message changed:', newValue)
      
      // 某些条件下停止监听
      if (newValue === 'stop') {
        stopWatcher()
      }
    })
    
    // 9. 监听器的返回值处理
    watch(count, async (newValue) => {
      console.log('Async operation start')
      
      try {
        const result = await fetch(`/api/data/${newValue}`)
        const data = await result.json()
        console.log('Data loaded:', data)
      } catch (error) {
        console.error('Failed to load data:', error)
      }
    })
    
    return {
      count,
      message,
      user
    }
  }
}

7.3 watchEffect的强大功能

watchEffect自动追踪其依赖并在依赖变化时重新执行:

import { ref, watchEffect, watchPostEffect, watchSyncEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const enabled = ref(true)
    const url = ref('/api/users')
    
    // 1. 基础watchEffect
    watchEffect(() => {
      console.log(`Count is: ${count.value}`)
      // 自动追踪count的变化
    })
    
    // 2. 条件性追踪
    watchEffect(() => {
      if (enabled.value) {
        console.log(`Enabled count: ${count.value}`)
        // 只有当enabled为true时才追踪count
      }
    })
    
    // 3. 清理副作用
    watchEffect((onInvalidate) => {
      const timer = setTimeout(() => {
        console.log('Timer executed')
      }, 1000)
      
      // 清理函数
      onInvalidate(() => {
        clearTimeout(timer)
        console.log('Timer cleared')
      })
    })
    
    // 4. 异步操作
    watchEffect(async (onInvalidate) => {
      let cancelled = false
      
      onInvalidate(() => {
        cancelled = true
      })
      
      try {
        const response = await fetch(url.value)
        
        // 检查是否已被取消
        if (!cancelled) {
          const data = await response.json()
          console.log('Data loaded:', data)
        }
      } catch (error) {
        if (!cancelled) {
          console.error('Failed to load data:', error)
        }
      }
    })
    
    // 5. 停止监听
    const stop = watchEffect(() => {
      console.log('This will stop automatically')
      
      if (count.value >= 10) {
        stop()
      }
    })
    
    // 6. 调试依赖追踪
    watchEffect(() => {
      console.log(`Count: ${count.value}, URL: ${url.value}`)
    }, {
      onTrack(e) {
        console.log('Tracked:', e)
      },
      onTrigger(e) {
        console.log('Triggered:', e)
      }
    })
    
    return {
      count,
      enabled,
      url
    }
  }
}

7.4 watch vs watchEffect

export default {
  setup() {
    const searchText = ref('')
    const category = ref('all')
    const results = ref([])
    
    // 使用watch:明确指定依赖
    watch([searchText, category], async ([newSearch, newCategory]) => {
      if (newSearch.trim()) {
        results.value = await searchItems(newSearch, newCategory)
      } else {
        results.value = []
      }
    })
    
    // 使用watchEffect:自动追踪依赖
    watchEffect(async () => {
      if (searchText.value.trim()) {
        results.value = await searchItems(searchText.value, category.value)
      } else {
        results.value = []
      }
    })
    
    return {
      searchText,
      category,
      results
    }
  }
}

选择指南:

  • watch:当你需要明确控制哪些状态变化应该触发副作用时
  • watchEffect:当副作用函数只依赖其内部使用的响应式状态时

7.5 高级监听模式

// 防抖监听
function useDebouncedWatch(source, callback, options = {}) {
  const { delay = 300, ...watchOptions } = options
  
  let timeoutId
  
  return watch(source, (...args) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => {
      callback(...args)
    }, delay)
  }, watchOptions)
}

// 节流监听
function useThrottledWatch(source, callback, options = {}) {
  const { delay = 300, ...watchOptions } = options
  
  let lastExecTime = 0
  
  return watch(source, (...args) => {
    const now = Date.now()
    if (now - lastExecTime >= delay) {
      lastExecTime = now
      callback(...args)
    }
  }, watchOptions)
}

// 使用示例
export default {
  setup() {
    const searchInput = ref('')
    const scrollPosition = ref(0)
    
    // 防抖搜索
    useDebouncedWatch(searchInput, (newValue) => {
      console.log('Search for:', newValue)
      // 执行搜索
    }, { delay: 500 })
    
    // 节流滚动
    useThrottledWatch(scrollPosition, (newPosition) => {
      console.log('Scroll position:', newPosition)
      // 处理滚动
    }, { delay: 100 })
    
    return {
      searchInput,
      scrollPosition
    }
  }
}

8. 生命周期组合式API

8.1 生命周期对比

Vue3提供了与Vue2生命周期对应的组合式API:

// Vue2 Options API vs Vue3 Composition API

export default {
  // Vue2生命周期
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted() {
    console.log('mounted')
  },
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() { // Vue3中重命名
    console.log('beforeUnmount')
  },
  unmounted() { // Vue3中重命名
    console.log('unmounted')
  },
  
  // Vue3 Composition API
  setup() {
    // setup执行时相当于beforeCreate和created之间
    console.log('setup')
    
    onBeforeMount(() => {
      console.log('onBeforeMount')
    })
    
    onMounted(() => {
      console.log('onMounted')
    })
    
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate')
    })
    
    onUpdated(() => {
      console.log('onUpdated')
    })
    
    onBeforeUnmount(() => {
      console.log('onBeforeUnmount')
    })
    
    onUnmounted(() => {
      console.log('onUnmounted')
    })
    
    return {}
  }
}

8.2 生命周期实际应用

import { 
  ref, 
  onMounted, 
  onUpdated, 
  onUnmounted, 
  onBeforeUnmount,
  onActivated,
  onDeactivated,
  onErrorCaptured
} from 'vue'

export default {
  setup() {
    const element = ref(null)
    const data = ref([])
    const loading = ref(true)
    
    // 组件挂载后执行
    onMounted(async () => {
      console.log('组件已挂载,DOM可用')
      
      // 获取DOM元素
      console.log('Element:', element.value)
      
      // 初始化第三方库
      initializeThirdPartyLibrary(element.value)
      
      // 获取初始数据
      try {
        const response = await fetch('/api/data')
        data.value = await response.json()
      } catch (error) {
        console.error('数据加载失败:', error)
      } finally {
        loading.value = false
      }
      
      // 添加事件监听器
      window.addEventListener('resize', handleResize)
      window.addEventListener('scroll', handleScroll)
    })
    
    // 组件更新后执行
    onUpdated(() => {
      console.log('组件已更新,DOM已重新渲染')
      
      // 如果需要在DOM更新后执行某些操作
      updateThirdPartyLibrary()
    })
    
    // 组件卸载前清理
    onBeforeUnmount(() => {
      console.log('组件即将卸载,开始清理工作')
      
      // 清理定时器
      clearInterval(timer)
      
      // 取消网络请求
      abortController.abort()
    })
    
    // 组件卸载后清理
    onUnmounted(() => {
      console.log('组件已卸载')
      
      // 移除事件监听器
      window.removeEventListener('resize', handleResize)
      window.removeEventListener('scroll', handleScroll)
      
      // 清理第三方库
      destroyThirdPartyLibrary()
    })
    
    // Keep-alive组件激活时
    onActivated(() => {
      console.log('组件被激活')
      // 重新获取数据或重新启动定时器
    })
    
    // Keep-alive组件停用时
    onDeactivated(() => {
      console.log('组件被停用')
      // 暂停某些操作,保存状态
    })
    
    // 错误捕获
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err)
      console.error('错误组件:', instance)
      console.error('错误信息:', info)
      
      // 错误上报
      reportError(err, { instance, info })
      
      // 返回false阻止错误继续传播
      return false
    })
    
    // 定义一些工具函数
    let timer = null
    const abortController = new AbortController()
    
    const handleResize = () => {
      console.log('窗口大小改变')
    }
    
    const handleScroll = () => {
      console.log('页面滚动')
    }
    
    const initializeThirdPartyLibrary = (el) => {
      // 初始化第三方库
    }
    
    const updateThirdPartyLibrary = () => {
      // 更新第三方库
    }
    
    const destroyThirdPartyLibrary = () => {
      // 销毁第三方库
    }
    
    const reportError = (error, context) => {
      // 错误上报逻辑
    }
    
    return {
      element,
      data,
      loading
    }
  }
}

8.3 多个生命周期钩子

在同一个组件中可以多次调用同一个生命周期钩子:

export default {
  setup() {
    // 第一个onMounted
    onMounted(() => {
      console.log('First onMounted')
      initializeComponent()
    })
    
    // 第二个onMounted
    onMounted(() => {
      console.log('Second onMounted')
      loadData()
    })
    
    // 第三个onMounted
    onMounted(() => {
      console.log('Third onMounted')
      setupEventListeners()
    })
    
    // 多个onUnmounted用于不同的清理工作
    onUnmounted(() => {
      console.log('清理数据')
    })
    
    onUnmounted(() => {
      console.log('清理事件监听器')
    })
    
    onUnmounted(() => {
      console.log('清理第三方库')
    })
    
    return {}
  }
}

8.4 生命周期在自定义Hook中的使用

// 自定义Hook:useWindowSize
function useWindowSize() {
  const width = ref(window.innerWidth)
  const height = ref(window.innerHeight)
  
  const updateSize = () => {
    width.value = window.innerWidth
    height.value = window.innerHeight
  }
  
  onMounted(() => {
    window.addEventListener('resize', updateSize)
  })
  
  onUnmounted(() => {
    window.removeEventListener('resize', updateSize)
  })
  
  return {
    width: readonly(width),
    height: readonly(height)
  }
}

// 自定义Hook:useInterval
function useInterval(callback, delay) {
  const savedCallback = ref(callback)
  
  // 更新回调函数
  watch(() => callback, (newCallback) => {
    savedCallback.value = newCallback
  })
  
  onMounted(() => {
    if (delay !== null) {
      const timer = setInterval(() => {
        savedCallback.value()
      }, delay)
      
      onUnmounted(() => {
        clearInterval(timer)
      })
    }
  })
}

// 自定义Hook:useEventListener
function useEventListener(target, event, handler, options) {
  onMounted(() => {
    target.addEventListener(event, handler, options)
  })
  
  onUnmounted(() => {
    target.removeEventListener(event, handler, options)
  })
}

// 在组件中使用
export default {
  setup() {
    // 使用窗口大小Hook
    const { width, height } = useWindowSize()
    
    // 使用定时器Hook
    const count = ref(0)
    useInterval(() => {
      count.value++
    }, 1000)
    
    // 使用事件监听Hook
    useEventListener(window, 'keydown', (e) => {
      if (e.key === 'Escape') {
        console.log('Escape pressed')
      }
    })
    
    return {
      width,
      height,
      count
    }
  }
}

8.5 生命周期最佳实践

export default {
  setup() {
    // 1. 数据初始化
    const data = ref([])
    const loading = ref(true)
    const error = ref(null)
    
    // 2. 异步数据获取
    const fetchData = async () => {
      try {
        loading.value = true
        error.value = null
        
        const response = await fetch('/api/data')
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        data.value = await response.json()
      } catch (err) {
        error.value = err.message
        console.error('数据获取失败:', err)
      } finally {
        loading.value = false
      }
    }
    
    // 3. 组件挂载时获取数据
    onMounted(() => {
      fetchData()
    })
    
    // 4. 清理资源
    const cleanup = () => {
      // 清理定时器、事件监听器、网络请求等
    }
    
    onUnmounted(cleanup)
    
    // 5. 错误处理
    onErrorCaptured((err, instance, info) => {
      console.error('组件错误:', err)
      
      // 错误上报
      if (process.env.NODE_ENV === 'production') {
        // 发送错误到监控服务
      }
      
      return false
    })
    
    return {
      data,
      loading,
      error,
      fetchData
    }
  }
}

9. 模板语法与指令更新

9.1 Vue3模板语法改进

Vue3在保持向后兼容的同时,对模板语法进行了一些优化:

<template>
  <!-- Vue3支持多个根元素 -->
  <header>
    <h1>{
  { title }}</h1>
  </header>
  
  <main>
    <!-- 插值表达式 -->
    <p>{
  { message }}</p>
    <p>{
  { rawHtml }}</p>
    <div v-html="rawHtml"></div>
    
    <!-- 属性绑定 -->
    <div v-bind:id="dynamicId" v-bind:class="classObject">
      <img :src="imageSrc" :alt="imageAlt">
    </div>
    
    <!-- 事件监听 -->
    <button @click="handleClick">点击我</button>
    <button @click="handleClickWithArgs('hello', $event)">传参点击</button>
    
    <!-- 条件渲染 -->
    <div v-if="showContent">显示的内容</div>
    <div v-else>隐藏时显示的内容</div>
    
   

网站公告

今日签到

点亮在社区的每一天
去签到