【 Vue】 Vue3全面讲解文档

文章目录

    • [一、Vue3 概述](#一、Vue3 概述)
      • [1.1 Vue3 简介](#1.1 Vue3 简介)
      • [1.2 核心改进](#1.2 核心改进)
    • [二、创建 Vue3 工程](#二、创建 Vue3 工程)
    • [三、Vue3 核心语法](#三、Vue3 核心语法)
      • [3.1 Options API vs Composition API](#3.1 Options API vs Composition API)
      • [3.2 setup 函数](#3.2 setup 函数)
        • 基本使用
        • [setup 特性](#setup 特性)
        • [setup 与 Options API 的关系](#setup 与 Options API 的关系)
        • [setup 语法糖](#setup 语法糖)
      • [3.3 响应式数据](#3.3 响应式数据)
        • [ref - 创建响应式数据](#ref - 创建响应式数据)
        • [reactive - 创建响应式对象](#reactive - 创建响应式对象)
        • [ref 与 reactive 对比](#ref 与 reactive 对比)
        • [toRefs 与 toRef](#toRefs 与 toRef)
      • [3.4 计算属性与侦听器](#3.4 计算属性与侦听器)
      • [3.5 模板引用与 props](#3.5 模板引用与 props)
        • [模板引用--标签的 ref 属性](#模板引用--标签的 ref 属性)
        • Props
      • [3.6 生命周期](#3.6 生命周期)
      • [3.7 自定义 Hook](#3.7 自定义 Hook)
    • [四、Vue Router 4](#四、Vue Router 4)
      • [4.1 安装与配置](#4.1 安装与配置)
      • [4.2 路由使用](#4.2 路由使用)
      • [4.3 路由传参](#4.3 路由传参)
      • [4.4 编程式导航--脱离RouterLink实现路由跳转](#4.4 编程式导航--脱离RouterLink实现路由跳转)
      • [4.5 嵌套路由--children](#4.5 嵌套路由--children)
      • [4.6 重定向--redirect](#4.6 重定向--redirect)
    • [五、Pinia 状态管理](#五、Pinia 状态管理)
      • [5.1 安装与配置](#5.1 安装与配置)
      • [5.2 Store 定义](#5.2 Store 定义)
      • [5.3 组合式 Store](#5.3 组合式 Store)
      • [5.4 在组件中使用](#5.4 在组件中使用)
      • [5.5 数据持久化](#5.5 数据持久化)
      • [5.6 subscribe](#5.6 subscribe)
    • 六、组件通信
      • [6.1 Props / 自定义事件](#6.1 Props / 自定义事件)
      • [6.2 v-model](#6.2 v-model)
      • [6.3 Provide / Inject](#6.3 Provide / Inject)
      • [6.4 插槽](#6.4 插槽)
      • [6.5 事件总线(Mitt)](#6.5 事件总线(Mitt))
    • 七、高级特性
      • [7.1 响应式工具函数](#7.1 响应式工具函数)
      • [7.2 自定义 ref](#7.2 自定义 ref)
      • [7.3 Teleport 组件](#7.3 Teleport 组件)
      • [7.4 Suspense 组件](#7.4 Suspense 组件)
    • 八、最佳实践与注意事项
      • [8.1 性能优化](#8.1 性能优化)
      • [8.2 TypeScript 最佳实践](#8.2 TypeScript 最佳实践)
      • [8.3 代码组织](#8.3 代码组织)
      • [8.4 常见问题](#8.4 常见问题)
    • [九、Vue2 到 Vue3 迁移](#九、Vue2 到 Vue3 迁移)
      • [9.1 破坏性变化](#9.1 破坏性变化)
      • [9.2 迁移策略](#9.2 迁移策略)
      • [9.3 兼容性处理](#9.3 兼容性处理)

一、Vue3 概述

1.1 Vue3 简介

Vue3 于 2020年9月18日发布,代号 "One Piece"。与 Vue2 相比,Vue3 在性能、开发体验和 TypeScript 支持等方面都有显著提升。

主要亮点:

  • 打包体积减少 41%
  • 初次渲染快 55%,更新渲染快 133%
  • 内存占用减少 54%
  • 更好的 TypeScript 支持

1.2 核心改进

  1. 性能提升:使用 Proxy 替代 Object.defineProperty 实现响应式,重写虚拟 DOM 实现
  2. 组合式 API:引入 Composition API,更好的代码组织和复用
  3. TypeScript 支持:源码使用 TypeScript 重写,提供更好的类型支持
  4. 新的内置组件:Fragment、Teleport、Suspense 等

二、创建 Vue3 工程

2.1 基于 Vite 创建(推荐)

Vite 是新一代前端构建工具,具有极速的服务启动和热更新。

bash 复制代码
# 创建项目
npm create vue@latest

# 项目配置示例
√ Project name: vue3-demo
√ Add TypeScript? Yes
√ Add JSX Support? No
√ Add Vue Router? No
√ Add Pinia? No
√ Add Vitest? No
√ Add ESLint? Yes

2.2 基于 vue-cli 创建(维护模式)

bash 复制代码
# 确保 @vue/cli 版本在 4.5.0 以上
vue --version
npm install -g @vue/cli
vue create vue-demo
# 选择 Vue 3.x

2.3 项目结构说明

  • index.html:项目入口文件
  • main.ts:应用主文件
  • Vue3 通过 createApp 函数创建应用实例
typescript 复制代码
// main.ts
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

三、Vue3 核心语法

3.1 Options API vs Composition API

Vue2 - Options API:

  • 数据、方法、计算属性分散在不同选项中
  • 新增需求需修改多个位置
  • 代码组织不够灵活

Vue3 - Composition API:

  • 相关功能代码组织在一起
  • 更好的代码复用和逻辑提取
  • 更适合复杂组件开发

3.2 setup 函数

基本使用
vue 复制代码
<template>
  <div>{{ name }}</div>
</template>

<script>
export default {
  setup() {
    let name = '张三'
    
    function changeName() {
      name = '李四'
    }
    
    return { name, changeName }
  }
}
</script>
setup 特性
  • beforeCreate 之前执行
  • thisundefined
  • 返回对象可在模板中直接使用
  • 也可返回渲染函数
setup 与 Options API 的关系
  • Vue2 的配置(datamethos...)中可以访问到 setup中的属性、方法。
  • 但在setup不能访问到 Vue2的配置(datamethos...)。
  • 如果与Vue2冲突,则setup优先。
setup 语法糖
vue 复制代码
<script setup lang="ts">
// 变量和方法直接可用,无需返回
let name = '张三'
function changeName() {
  name = '李四'
}
</script>

设置组件名插件:

bash 复制代码
npm i vite-plugin-vue-setup-extend -D
typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [ VueSetupExtend() ]
})

使用:<script setup lang="ts" name="MyComponent">

3.3 响应式数据

ref - 创建响应式数据
vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'

// 基本类型
let count = ref(0)

// 对象类型
let user = ref({ name: '张三', age: 20 })

// JS 中需要 .value,模板中不需要
function increment() {
  count.value++
  user.value.age++
}
</script>
reactive - 创建响应式对象
vue 复制代码
<script setup lang="ts">
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: { name: '张三', age: 20 }
})

function update() {
  state.count++
  state.user.age++
}
</script>
ref 与 reactive 对比
特性 ref reactive
数据类型 基本类型 + 对象类型 仅对象类型
访问方式 .value 直接访问
重新赋值 保持响应式 失去响应式,使用Object.assign去整体替换
深度监听 默认浅层,需手动开启 默认深度

使用原则:

  1. 基本类型 → ref
  2. 简单对象 → ref/reactive 都可以
  3. 深层嵌套对象 → reactive
toRefs 与 toRef
  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。
  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。
vue 复制代码
<script setup lang="ts">
import { reactive, toRefs, toRef } from 'vue'

const state = reactive({
  name: '张三',
  age: 20,
  address: { city: '北京' }
})

// 批量解构保持响应式
const { name, age } = toRefs(state)

// 单个属性保持响应式
const city = toRef(state.address, 'city')
</script>

3.4 计算属性与侦听器

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

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

// 只读计算属性
const fullName = computed(() => `${firstName.value}${lastName.value}`)

// 可读可写计算属性
const fullName2 = computed({
  get() {
    return `${firstName.value}${lastName.value}`
  },
  set(val) {
    const [first, last] = val.split('-')
    firstName.value = first
    lastName.value = last
  }
})
</script>
watch
  • 作用:监视数据的变化(和Vue2中的watch作用一致)
  • 特点:Vue3中的watch只能监视以下四种数据
  1. ref定义的数据。
  2. reactive定义的数据。
  3. 函数返回一个值(getter函数)。
  4. 一个包含上述内容的数组。
vue 复制代码
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'

// 1. 监视 ref 基本类型
const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log('count变化:', newVal, oldVal)
})

// 2. 监视 ref 对象类型--需要手动开启深度监视
/* 
    监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
    watch的第一个参数是:被监视的数据
    watch的第二个参数是:监视的回调
    watch的第三个参数是:配置对象(deep、immediate等等.....) 
  */
const user = ref({ name: '张三', age: 20 })
watch(user, (newVal, oldVal) => {
  console.log('user变化:', newVal, oldVal)
}, { deep: true })

// 3. 监视 reactive 对象--默认是开启深度监视的
const state = reactive({ count: 0 })
watch(() => state.count, (newVal, oldVal) => {
  console.log('state.count变化:', newVal, oldVal)
})

// 4. 监视多个数据
watch([() => state.count, count], (newVals, oldVals) => {
  console.log('多个数据变化:', newVals, oldVals)
})
</script>
watchEffect
vue 复制代码
<script setup lang="ts">
import { ref, watchEffect } from 'vue'

const temp = ref(10)
const height = ref(0)

// 自动追踪依赖
watchEffect(() => {
  if (temp.value >= 60 || height.value >= 80) {
    console.log('触发报警')
  }
})
</script>

3.5 模板引用与 props

模板引用--标签的 ref 属性
vue 复制代码
<template>
  <!-- DOM 元素引用 -->
  <input ref="inputRef" />
  
  <!-- 组件引用 -->
  <ChildComponent ref="childRef" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import ChildComponent from './Child.vue'

const inputRef = ref<HTMLInputElement>()
const childRef = ref<InstanceType<typeof ChildComponent>>()

onMounted(() => {
  inputRef.value?.focus()
  console.log(childRef.value?.someMethod())
})
</script>
Props
typescript 复制代码
// types.ts - 类型定义
export interface Person {
  id: string
  name: string
  age: number
}

export type Persons = Person[]
vue 复制代码
<!-- 父组件 -->
<template>
  <Child :list="personList" />
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { type Persons } from './types'

const personList = reactive<Persons>([
  { id: '1', name: '张三', age: 20 },
  { id: '2', name: '李四', age: 25 }
])
</script>
vue 复制代码
<!-- 子组件 -->
<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      {{ item.name }} - {{ item.age }}
    </li>
  </ul>
</template>

<script setup lang="ts">
import { withDefaults } from 'vue'
import { type Persons } from '@/types'

  // 第一种写法:仅接收
// const props = defineProps(['list'])
  
  // 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
  
  // 第三种写法:接收+限制类型+指定默认值+限制必要性
const props = withDefaults(defineProps<{ list?: Persons }>(), {
  list: () => [{id:'asdasg01',name:'小猪佩奇',age:18}]
})
</script>

3.6 生命周期

生命周期对比:
  • Vue2的生命周期

    创建阶段:beforeCreatecreated

    挂载阶段:beforeMountmounted

    更新阶段:beforeUpdateupdated

    销毁阶段:beforeDestroydestroyed

  • Vue3的生命周期

    创建阶段:setup

    挂载阶段:onBeforeMountonMounted

    更新阶段:onBeforeUpdateonUpdated

    卸载阶段:onBeforeUnmountonUnmounted

Vue3 生命周期钩子需要从 vue 中导入:

vue 复制代码
<script setup lang="ts">
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

// 常用钩子
onMounted(() => {
  console.log('组件挂载完成')
})

onUpdated(() => {
  console.log('组件更新完成')
})

onBeforeUnmount(() => {
  console.log('组件卸载前')
})
</script>

3.7 自定义 Hook

typescript 复制代码
// hooks/useSum.ts
import {ref,onMounted} from 'vue'

export default function(){
  let sum = ref(0)

  const increment = ()=>{
    sum.value += 1
  }
  const decrement = ()=>{
    sum.value -= 1
  }
  onMounted(()=>{
    increment()
  })

  //向外部暴露数据
  return {sum,increment,decrement}
}		
typescript 复制代码
// hooks/useDog.ts
import {reactive,onMounted} from 'vue'
import axios,{AxiosError} from 'axios'

export default function(){
  let dogList = reactive<string[]>([])

  // 方法
  async function getDog(){
    try {
      // 发请求
      let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
      // 维护数据
      dogList.push(data.message)
    } catch (error) {
      // 处理错误
      const err = <AxiosError>error
      console.log(err.message)
    }
  }

  // 挂载钩子
  onMounted(()=>{
    getDog()
  })
	
  //向外部暴露数据
  return {dogList,getDog}
}
vue 复制代码
<template>
  <h2>当前求和为:{{sum}}</h2>
  <button @click="increment">点我+1</button>
  <button @click="decrement">点我-1</button>
  <hr>
  <img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)"> 
  <span v-show="dogList.isLoading">加载中......</span><br>
  <button @click="getDog">再来一只狗</button>
</template>

<script lang="ts">
  import {defineComponent} from 'vue'

  export default defineComponent({
    name:'App',
  })
</script>

<script setup lang="ts">
 // 使用hook
  import useSum from './hooks/useSum'
  import useDog from './hooks/useDog'
	
  let {sum,increment,decrement} = useSum()
  let {dogList,getDog} = useDog()
</script>

四、Vue Router 4

4.1 安装与配置

bash 复制代码
npm install vue-router@4
typescript 复制代码
// router/index.ts
// 第一步:引入createRouter
import { createRouter, createWebHistory } from 'vue-router'
// 引入一个一个可能要呈现的组件
import Home from '../views/Home.vue'
// import About from '@/components/About.vue'
// 第二步:创建路由器
const router = createRouter({
  // history 模式
  history: createWebHistory(),
  // hash 模式: createWebHashHistory()
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('../views/About.vue') // 路由懒加载
    }
  ]
})

export default router
typescript 复制代码
//main.ts
import router from './router/index'
app.use(router)

app.mount('#app')

4.2 路由使用

  • 路由组件通常存放在pagesviews文件夹,一般组件通常存放在components文件夹。
  • 路由器工作模式:
模式 创建方式 优点 缺点
history 模式 createWebHistory() URL 美观,无 #,利于 SEO 需要服务器配置,否则刷新会 404
hash 模式 createWebHashHistory() 无需服务器配置,兼容性好 URL 有 #,不美观,不利于 SEO
  • to的两种写法
typescript 复制代码
<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>

<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
vue 复制代码
<template>
  <!-- 导航链接 -->
  <router-link to="/">首页</router-link>
  <router-link :to="{ name: 'About' }">关于</router-link>
  
  <!-- 路由出口 -->
  <router-view />
</template>

<script lang="ts" setup name="App">
  import {RouterLink,RouterView} from 'vue-router'  
</script>

4.3 路由传参

备注1:传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path

备注2:传递params参数时,需要提前在规则中占位。

vue 复制代码
<!-- Query 参数 -->
<router-link :to="{ path: '/user', query: { id: 1 } }">用户</router-link>

<!-- Params 参数 -->
<router-link :to="{ name: 'User', params: { id: 1 } }">用户</router-link>
vue 复制代码
<script setup lang="ts">
import { useRoute } from 'vue-router'

const route = useRoute()

// 获取参数
const id = route.params.id
const query = route.query
</script>

4.4 编程式导航--脱离RouterLink实现路由跳转

  • 浏览器的历史记录有两种写入方式:分别为pushreplace

    • push是追加历史记录(默认值)。
    • replace是替换当前记录。
vue 复制代码
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

// 导航方法
function goToUser(id: number) {
  router.push(`/user/${id}`)
  // 或
  router.push({ name: 'User', params: { id } })
}

// 替换当前路由
router.replace('/home')

// 前进后退
router.go(1)
router.back()

// console.log(route.query)
// console.log(route.parmas)
// console.log(router.push)
// console.log(router.replace)


</script>

4.5 嵌套路由--children

typescript 复制代码
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        path: 'profile',
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      }
    ]
  }
]

4.6 重定向--redirect

typescript 复制代码
{
    path:'/',
    redirect:'/about'
}

五、Pinia 状态管理

5.1 安装与配置

bash 复制代码
npm install pinia
typescript 复制代码
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

5.2 Store 定义

  1. Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。
  2. 它有三个概念:stategetteraction,相当于组件中的: datacomputedmethods
  3. 具体编码:src/store/counter.ts
typescript 复制代码
// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Counter'
  }),
  
  getters: {
    doubleCount: (state) => state.count * 2,
    greeting(): string {
      return `Hello ${this.name}`
    }
  },
  
  actions: {
    increment() {
      this.count++
    },
    async fetchData() {
      const response = await fetch('/api/data')
      // 更新 state
    }
  }
})

5.3 组合式 Store

typescript 复制代码
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  const name = ref('')
  const age = ref(0)
  
  const isAdult = computed(() => age.value >= 18)
  
  function updateUser(newName: string, newAge: number) {
    name.value = newName
    age.value = newAge
  }
  
  return { name, age, isAdult, updateUser }
})

5.4 在组件中使用

  • 借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。
  • 注意:pinia提供的storeToRefs只会将数据做转换,而VuetoRefs会转换store中全部数据,包括方法。
vue 复制代码
<template>
  <div>{{ counter.count }}</div>
  <div>{{ counter.doubleCount }}</div>
  <button @click="counter.increment()">+</button>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const counter = useCounterStore()

// 解构保持响应式
const { count, doubleCount } = storeToRefs(counter)
</script>

5.5 数据持久化

typescript 复制代码
import { defineStore } from 'pinia'

export const useStore = defineStore('main', {
  state: () => ({
    data: JSON.parse(localStorage.getItem('store-data') || 'null') || {}
  }),
  
  actions: {
    saveToLocalStorage() {
      localStorage.setItem('store-data', JSON.stringify(this.data))
    }
  }
})

5.6 $subscribe

概述

$subscribe 是 Pinia 提供的一个方法,用于监听 store 中 state 的变化。它类似于 Vue 的 watch,但专门用于监听 Pinia store 的状态变化。

基本使用
ts 复制代码
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++
    }
  }
})
ts 复制代码
// 在组件中监听 state 变化
const counterStore = useCounterStore()

// 监听整个 store 的变化
counterStore.$subscribe((mutation, state) => {
  console.log('Store 变化:', mutation, state)
  
  // 保存到 localStorage
  localStorage.setItem('counter-state', JSON.stringify(state))
})

六、组件通信

通信方式 Vue2 Vue3 变化说明
父子通信 props / $emit defineProps / defineEmits 声明方式变化,TypeScript 支持更好
事件总线 EventBus mitt 移除内置,使用第三方库
状态管理 Vuex Pinia 更简洁、组合式 API
双向绑定 .sync 修饰符 v-model 参数 语法统一,支持多个
跨级通信 provide/inject 选项 provide/inject 函数 组合式 API
属性透传 $attrs + $listeners 合并到 $attrs 简化 API
子组件访问 $children + $refs ref + defineExpose 移除 $children,更明确
插槽 slot / slot-scope v-slot 语法统一
作用域插槽 slot-scope v-slot + 解构 语法更简洁

6.1 Props / 自定义事件

ts 复制代码
<!-- 父组件 -->
<template>
  <Child :msg="message" @update="handleUpdate" />
</template>

<script setup lang="ts">
const message = ref('Hello')
function handleUpdate(newMsg: string) {
  message.value = newMsg
}
</script>

<!-- 子组件 -->
<template>
  <button @click="emit('update', 'New Message')">更新</button>
</template>

<script setup lang="ts">
defineProps<{
  msg: string
}>()

const emit = defineEmits<{
  (e: 'update', value: string): void
}>()
</script>

6.2 v-model

ts 复制代码
<!-- 父组件 -->
<template>
  <CustomInput v-model="text" />
  <CustomInput v-model:title="title" />
</template>

<!-- 子组件 -->
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
</template>

<script setup lang="ts">
defineProps<{
  modelValue: string
  title?: string
}>()

defineEmits<{
  'update:modelValue': [value: string]
  'update:title': [value: string]
}>()
</script>

6.3 Provide / Inject

ts 复制代码
<!-- 祖先组件 -->
<script setup lang="ts">
import { provide, ref } from 'vue'

const theme = ref('dark')
provide('theme', theme)
</script>

<!-- 后代组件 -->
<script setup lang="ts">
import { inject } from 'vue'

const theme = inject('theme', ref('light'))
</script>

6.4 插槽

ts 复制代码
<!-- 父组件 -->
<template>
  <MyComponent>
    <template #header>
      <h1>标题</h1>
    </template>
    
    <template #default="{ user }">
      <p>{{ user.name }}</p>
    </template>
    
    <template #footer>
      <p>页脚</p>
    </template>
  </MyComponent>
</template>

<!-- 子组件 -->
<template>
  <div>
    <slot name="header" />
    <slot :user="currentUser" />
    <slot name="footer" />
  </div>
</template>

6.5 事件总线(Mitt)

typescript 复制代码
// utils/emitter.ts
import mitt from 'mitt'
type Events = {
  'user-login': { userId: number }
  'user-logout': void
}
export const emitter = mitt<Events>()
vue 复制代码
<!-- 发送事件 -->
<script setup lang="ts">
import { emitter } from '@/utils/emitter'

function login() {
  emitter.emit('user-login', { userId: 1 })
}
</script>

<!-- 接收事件 -->
<script setup lang="ts">
import { emitter } from '@/utils/emitter'
import { onUnmounted } from 'vue'

const handleLogin = (data: { userId: number }) => {
  console.log('用户登录:', data.userId)
}

emitter.on('user-login', handleLogin)

onUnmounted(() => {
  emitter.off('user-login', handleLogin)
})
</script>

七、高级特性

7.1 响应式工具函数

typescript 复制代码
import {
  shallowRef,      // 浅层 ref
  shallowReactive, // 浅层 reactive
  readonly,        // 深只读
  shallowReadonly, // 浅只读
  toRaw,           // 获取原始对象
  markRaw          // 标记为非响应式
} from 'vue'

// 浅层响应式
const shallowObj = shallowReactive({ nested: { count: 0 } })
shallowObj.nested.count = 1 // 不会触发响应

// 只读
const readOnlyData = readonly({ value: 1 })
// readOnlyData.value = 2 // 错误

// 获取原始对象
const raw = toRaw(reactiveObj)

// 标记为非响应式
const nonReactive = markRaw({ value: 1 })

7.2 自定义 ref

typescript 复制代码
import { customRef } from 'vue'

function useDebouncedRef<T>(value: T, delay = 200) {
  let timer: number
  
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

// 使用
const text = useDebouncedRef('', 300)

7.3 Teleport 组件

vue 复制代码
<template>
  <button @click="show = true">打开弹窗</button>
  
  <Teleport to="body">
    <div v-if="show" class="modal">
      <h2>弹窗标题</h2>
      <button @click="show = false">关闭</button>
    </div>
  </Teleport>
</template>

7.4 Suspense 组件

vue 复制代码
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

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

八、最佳实践与注意事项

8.1 性能优化

  1. 合理使用响应式:避免不必要的响应式数据
  2. 组件懒加载defineAsyncComponent + Suspense
  3. 列表优化 :使用 key,避免 v-ifv-for 一起使用
  4. 计算属性缓存 :复杂计算使用 computed
  5. 事件防抖节流:使用 Lodash 或自定义实现

8.2 TypeScript 最佳实践

  1. 明确类型 :避免使用 any
  2. 类型导入 :使用 import type 导入纯类型
  3. 泛型组件:提供更好的类型推断
  4. 类型文件组织 :合理组织 .d.ts 文件

8.3 代码组织

  1. 单一职责:组件功能单一
  2. 自定义 Hook:逻辑复用
  3. 组件通信:根据场景选择合适的通信方式
  4. 目录结构:按功能模块组织代码

8.4 常见问题

  1. 响应式丢失 :解构时使用 toRefs
  2. 内存泄漏:及时清理事件监听器和定时器
  3. 路由守卫:合理使用路由导航守卫
  4. 错误处理:全局错误处理和组件内错误边界

九、Vue2 到 Vue3 迁移

9.1 破坏性变化

  1. v-model 用法变更
  2. 事件 API 变更(移除 $on, $off, $once
  3. 过滤器(filter)移除
  4. 生命周期钩子命名变更
  5. 异步组件写法变更

9.2 迁移策略

  1. 使用 Vue3 兼容版本
  2. 逐步迁移组件
  3. 使用迁移工具辅助
  4. 充分测试确保稳定性

9.3 兼容性处理

javascript 复制代码
// vue.config.js - Vue CLI 项目
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        vue$: 'vue/dist/vue.esm-bundler.js'
      }
    }
  }
}
相关推荐
换日线°2 小时前
vue 实现Element Plus的炫酷主题切换
javascript·vue.js
linweidong2 小时前
大厂工程化实践:如何构建可运维、高稳定性的 Flutter 混合体系
javascript·flutter
Hexene...2 小时前
【前端Vue】如何快速直观地查看引入的前端依赖?名称版本、仓库地址、开源协议、作者、依赖介绍、关系树...(Node Modules Inspector)
前端·javascript·vue.js
fanruitian2 小时前
div水平垂直居中
前端·javascript·html
旭久2 小时前
react+antd实现一个支持多种类型及可新增编辑搜索的下拉框
前端·javascript·react.js
摘星编程2 小时前
用React Native开发OpenHarmony应用:Loading加载状态组件
javascript·react native·react.js
aesthetician2 小时前
Spotify 网页版前端技术全面解析
前端
咩图2 小时前
Sketchup软件二次开发+Ruby+VisualStudioCode
java·前端·ruby
Можно2 小时前
从零开始:Vue 框架安装全指南
前端·javascript·vue.js