Skip to content

Vue3 开发指南

🌟 简介

Vue3 是 Vue.js 的最新主要版本,带来了革命性的组合式 API、显著的性能提升和丰富的新特性。本文将全面介绍 Vue3 的核心概念、实战技巧和最佳实践,帮助你快速掌握这一现代前端框架。

🚀 组合式 API

1.1 基础用法

组合式 API 是 Vue3 的核心特性,它允许我们按照逻辑关注点组织代码,而不是按照选项类型。下面是一个基本的使用示例:

javascript
// 导入必要的组合式 API
import { ref, computed, onMounted } from 'vue'

export default {
  // setup 函数是组合式 API 的入口点
  // 在组件实例创建之前执行,返回的内容会暴露给模板
  setup() {
    // 使用 ref 创建响应式数据(适用于基本类型)
    // ref 返回一个响应式的引用对象,需要通过 .value 访问和修改
    const count = ref(0)
    
    // 使用 computed 创建计算属性
    // 计算属性会缓存结果,只有依赖项变化时才会重新计算
    const doubleCount = computed(() => count.value * 2)
    
    // 定义方法
    function increment() {
      // 修改 ref 包装的值需要使用 .value
      count.value++
    }
    
    // 使用生命周期钩子
    // 组合式 API 中的生命周期钩子以 on 开头
    onMounted(() => {
      console.log('Component mounted')
    })
    
    // 返回需要暴露给模板的内容
    return {
      count,
      doubleCount,
      increment
    }
  }
}

1.2 组合式函数

组合式函数是 Vue3 中代码复用的最佳方式,它允许我们将相关的响应式状态和逻辑封装成可复用的函数。下面是一个计数器组合式函数的示例:

javascript
// useCounter.js
import { ref, computed } from 'vue'

/**
 * 计数器组合式函数
 * @param {number} initialValue - 初始值,默认为 0
 * @returns {Object} 包含计数状态和操作方法的对象
 */
export function useCounter(initialValue = 0) {
  // 创建响应式计数状态
  const count = ref(initialValue)
  
  // 创建计算属性,实时计算双倍值
  const doubleCount = computed(() => count.value * 2)
  
  // 增加计数的方法
  function increment() {
    count.value++
  }
  
  // 减少计数的方法
  function decrement() {
    count.value--
  }
  
  // 返回需要暴露的状态和方法
  return {
    count,
    doubleCount,
    increment,
    decrement
  }
}

// 使用组合式函数
import { useCounter } from './useCounter'

export default {
  setup() {
    // 调用组合式函数,传入初始值 10
    // 解构获取需要的状态和方法
    const { count, doubleCount, increment } = useCounter(10)
    
    return {
      count,
      doubleCount,
      increment
    }
  }
}

🔄 响应式系统

2.1 ref 和 reactive

Vue3 提供了两种创建响应式数据的方式:refreactive。它们各有适用场景,下面是详细的使用示例:

javascript
import { ref, reactive } from 'vue'

export default {
  setup() {
    // ref 适用于基本类型(数字、字符串、布尔值等)
    // 也可以用于对象,但需要通过 .value 访问
    const count = ref(0)
    
    // reactive 适用于对象和数组
    // 直接返回一个响应式的代理对象,不需要 .value
    const state = reactive({
      user: {
        name: 'John',
        age: 30
      },
      todos: []
    })
    
    // 修改响应式数据
    function updateUser() {
      // 修改 ref 包装的值需要使用 .value
      // count.value = 1
      
      // 修改 reactive 包装的对象直接赋值即可
      state.user.name = 'Jane'
      state.user.age = 25
    }
    
    // 返回需要暴露给模板的内容
    return {
      count,
      state,
      updateUser
    }
  }
}

2.2 计算属性和侦听器

计算属性和侦听器是 Vue 中处理响应式数据的重要工具,它们分别用于不同的场景:

javascript
import { ref, reactive, computed, watch } from 'vue'

export default {
  setup() {
    // 创建响应式数据
    const count = ref(0)
    const state = reactive({
      user: {
        name: 'John',
        age: 30
      }
    })
    
    // 计算属性
    // 用于根据其他响应式数据派生新值
    // 会缓存结果,只有依赖项变化时才重新计算
    const doubleCount = computed(() => count.value * 2)
    
    // 基本侦听器
    // 用于监听响应式数据的变化并执行副作用
    watch(count, (newValue, oldValue) => {
      console.log('count changed:', oldValue, '->', newValue)
      // 可以在这里执行异步操作、API 调用等
    })
    
    // 深度侦听
    // 用于监听嵌套对象的变化
    // 需要设置 deep: true 选项
    watch(
      () => state.user, // 使用函数返回值作为侦听源
      (newValue, oldValue) => {
        console.log('user changed:', oldValue, '->', newValue)
      },
      { deep: true }
    )
    
    // 立即执行的侦听器
    // 用于在组件创建时立即执行一次
    watch(
      count,
      (newValue) => {
        console.log('Current count:', newValue)
      },
      { immediate: true }
    )
    
    return {
      count,
      doubleCount,
      state
    }
  }
}

⏰ 生命周期钩子

Vue3 提供了与 Vue2 对应的生命周期钩子,但在组合式 API 中,它们以函数形式存在,并且需要从 Vue 中导入。以下是完整的生命周期钩子使用示例:

javascript
import {
  onMounted,
  onUpdated,
  onUnmounted,
  onBeforeMount,
  onBeforeUpdate,
  onBeforeUnmount,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered
} from 'vue'

export default {
  setup() {
    // 挂载前
    onBeforeMount(() => {
      console.log('Before mount')
    })
    
    // 挂载后
    onMounted(() => {
      console.log('Mounted')
    })
    
    // 更新前
    onBeforeUpdate(() => {
      console.log('Before update')
    })
    
    // 更新后
    onUpdated(() => {
      console.log('Updated')
    })
    
    // 卸载前
    onBeforeUnmount(() => {
      console.log('Before unmount')
    })
    
    // 卸载后
    onUnmounted(() => {
      console.log('Unmounted')
    })
    
    // 错误捕获
    onErrorCaptured((error) => {
      console.error('Error captured:', error)
      return true // 阻止错误继续传播
    })
    
    // 渲染跟踪
    onRenderTracked((event) => {
      console.log('Render tracked:', event)
    })
    
    // 渲染触发
    onRenderTriggered((event) => {
      console.log('Render triggered:', event)
    })
  }
}

📝 模板语法

4.1 指令

vue
<template>
  <!-- v-model -->
  <input v-model="message" />
  
  <!-- v-if -->
  <div v-if="isVisible">Visible content</div>
  
  <!-- v-for -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
  
  <!-- v-on -->
  <button @click="handleClick">Click me</button>
  
  <!-- v-bind -->
  <img :src="imageUrl" :alt="imageAlt" />
</template>

<script setup>
import { ref } from 'vue'

const message = ref('')
const isVisible = ref(true)
const items = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
])
const imageUrl = ref('/path/to/image.jpg')
const imageAlt = ref('Image description')

function handleClick() {
  console.log('Button clicked')
}
</script>

4.2 插槽

vue
<!-- 子组件 -->
<template>
  <div class="card">
    <header>
      <slot name="header">Default header</slot>
    </header>
    <main>
      <slot>Default content</slot>
    </main>
    <footer>
      <slot name="footer">Default footer</slot>
    </footer>
  </div>
</template>

<!-- 父组件 -->
<template>
  <Card>
    <template #header>
      <h1>Custom header</h1>
    </template>
    
    <p>Custom content</p>
    
    <template #footer>
      <p>Custom footer</p>
    </template>
  </Card>
</template>

🗃️ 状态管理

5.1 Pinia

javascript
// store/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    async fetchCount() {
      const response = await fetch('/api/count')
      this.count = await response.json()
    }
  }
})

// 使用 store
import { useCounterStore } from '@/store/counter'

export default {
  setup() {
    const counter = useCounterStore()
    
    return {
      count: counter.count,
      doubleCount: counter.doubleCount,
      increment: counter.increment
    }
  }
}

5.2 组合式 API 状态管理

javascript
// composables/useStore.js
import { reactive } from 'vue'

export function useStore() {
  const state = reactive({
    count: 0,
    todos: []
  })
  
  function increment() {
    state.count++
  }
  
  function addTodo(todo) {
    state.todos.push(todo)
  }
  
  return {
    state,
    increment,
    addTodo
  }
}

⚡ 性能优化

6.1 组件优化

javascript
// 异步组件
import { defineAsyncComponent } from 'vue'

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

// 组件缓存
<template>
  <keep-alive>
    <component :is="currentComponent" />
  </keep-alive>
</template>

// 虚拟列表
<template>
  <virtual-scroller
    :items="items"
    :item-height="50"
  >
    <template v-slot="{ item }">
      <div class="item">{{ item.text }}</div>
    </template>
  </virtual-scroller>
</template>

6.2 渲染优化

javascript
// 使用 shallowRef
import { shallowRef } from 'vue'

const state = shallowRef({
  user: {
    name: 'John',
    address: {
      city: 'New York'
    }
  }
})

// 使用 markRaw
import { markRaw } from 'vue'

const rawObject = markRaw({
  complex: true,
  nested: {
    data: 'value'
  }
})

🎯 最佳实践

7.1 项目结构

合理的项目结构是构建可维护应用的基础,以下是一个推荐的 Vue3 项目结构及其用途说明:

bash
src/
  components/           # 组件目录
    Base/              # 基础组件(按钮、输入框等)
    Layout/            # 布局组件(头部、侧边栏等)
    Business/          # 业务组件(特定功能模块)
  composables/         # 组合式函数目录
  stores/             # 状态管理目录(Pinia store)
  views/              # 页面组件目录
  router/             # 路由配置目录
  utils/              # 工具函数目录
  assets/             # 静态资源目录(图片、字体等)
  styles/             # 全局样式目录
  services/           # API 服务目录
  types/              # TypeScript 类型定义目录

实际应用场景:

  • 大型项目:可以按照功能模块进一步划分目录,如 components/user/components/product/
  • 团队协作:明确的目录结构有助于团队成员快速定位和理解代码
  • 代码复用:将通用逻辑提取到 composables/utils/ 目录,提高代码复用率

7.2 代码组织

在 Vue3 中,组合式 API 为我们提供了更灵活的代码组织方式。以下是一个用户认证组合式函数的实际应用示例:

javascript
// composables/useUser.js
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'

/**
 * 用户认证组合式函数
 * 封装用户登录、登出和状态管理逻辑
 */
export function useUser() {
  // 用户信息状态
  const user = ref(null)
  // 登录状态计算属性
  const isLoggedIn = computed(() => !!user.value)
  // 加载状态
  const isLoading = ref(false)
  // 错误信息
  const error = ref(null)
  // 路由实例
  const router = useRouter()
  
  /**
   * 用户登录
   * @param {Object} credentials - 登录凭证
   * @param {string} credentials.username - 用户名
   * @param {string} credentials.password - 密码
   */
  async function login(credentials) {
    isLoading.value = true
    error.value = null
    
    try {
      // 模拟 API 调用
      // const response = await api.login(credentials)
      // user.value = response.data
      
      // 模拟登录成功
      user.value = {
        id: 1,
        username: credentials.username,
        name: '测试用户'
      }
      
      // 登录成功后跳转到首页
      router.push('/')
    } catch (err) {
      error.value = '登录失败,请检查用户名和密码'
      console.error('Login error:', err)
    } finally {
      isLoading.value = false
    }
  }
  
  /**
   * 用户登出
   */
  async function logout() {
    isLoading.value = true
    
    try {
      // 模拟 API 调用
      // await api.logout()
      
      // 清除用户信息
      user.value = null
      
      // 跳转到登录页
      router.push('/login')
    } catch (err) {
      console.error('Logout error:', err)
    } finally {
      isLoading.value = false
    }
  }
  
  /**
   * 初始化用户信息
   * 用于页面刷新后恢复用户状态
   */
  async function initUser() {
    isLoading.value = true
    
    try {
      // 模拟从本地存储或 API 获取用户信息
      // const savedUser = localStorage.getItem('user')
      // if (savedUser) {
      //   user.value = JSON.parse(savedUser)
      // }
    } catch (err) {
      console.error('Init user error:', err)
    } finally {
      isLoading.value = false
    }
  }
  
  // 返回状态和方法
  return {
    user,
    isLoggedIn,
    isLoading,
    error,
    login,
    logout,
    initUser
  }
}

// 使用组合式函数
import { useUser } from '@/composables/useUser'
import { onMounted } from 'vue'

export default {
  setup() {
    const { user, isLoggedIn, isLoading, error, login, logout, initUser } = useUser()
    
    // 组件创建时初始化用户信息
    onMounted(() => {
      initUser()
    })
    
    return {
      user,
      isLoggedIn,
      isLoading,
      error,
      login,
      logout
    }
  }
}

实际应用场景:

  • 跨组件复用:在登录页、用户信息页等多个组件中复用认证逻辑
  • 逻辑关注点分离:将用户认证相关的所有逻辑集中在一个地方,便于维护
  • 类型安全:结合 TypeScript 可以为组合式函数添加类型定义,提高代码可靠性
  • 测试友好:组合式函数可以单独测试,不需要渲染整个组件

🛠️ 工具链

8.1 开发工具

json
{
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.0.0",
    "vue": "^3.2.0",
    "pinia": "^2.0.0",
    "vue-router": "^4.0.0",
    "typescript": "^4.5.0",
    "eslint": "^8.0.0",
    "prettier": "^2.0.0"
  }
}

8.2 测试工具

json
{
  "devDependencies": {
    "@vue/test-utils": "^2.0.0",
    "vitest": "^0.34.0",
    "@testing-library/vue": "^7.0.0",
    "cypress": "^12.0.0"
  }
}

📋 总结

Vue3 作为现代前端框架的佼佼者,通过以下核心特性为我们带来了革命性的开发体验:

  1. 组合式 API:提供了更灵活、更可复用的代码组织方式,彻底解决了 Options API 中逻辑分散的问题
  2. 响应式系统升级:基于 Proxy 的响应式系统,提供了更好的性能和更直观的 API
  3. 性能优化:编译时优化、虚拟 DOM 改进、Tree-shaking 支持等,显著提升了应用性能
  4. TypeScript 支持:更好的类型推断和类型检查,提高了代码的可靠性
  5. 生态系统完善:Pinia 状态管理、Vite 构建工具等,形成了完整的开发生态

通过本文介绍的最佳实践和实际应用场景,你可以充分发挥 Vue3 的优势,构建出高效、可维护的现代化前端应用。

📚 参考资源

官方文档

学习资源

工具与插件

社区资源