Vue 3 从基础到高阶全攻略

Vue 3 从基础到高阶全攻略

探索 Vue 3 的无限可能 🚀

引言

Vue 3 作为当前最流行的前端框架之一,带来了许多令人振奋的新特性和性能改进。从组合式 API 到更好的 TypeScript 支持,从更小的打包体积到更快的渲染速度,Vue 3 为前端开发者提供了更现代、更高效的开发体验。本文将带你从基础到高阶,全面掌握 Vue 3 的核心技术!

一、快速上手

环境搭建

使用 Vite 创建项目
# npm
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue
安装依赖并启动
cd my-vue-app
npm install
npm run dev

第一个 Vue 3 应用

<!-- App.vue -->
<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="count++">点击次数: {{ count }}</button>
  </div>
</template>

<script setup>
// 响应式数据
import { ref } from 'vue'

const message = 'Hello Vue 3!'
const count = ref(0)
</script>

<style scoped>
button {
  padding: 8px 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #35495e;
}
</style>

二、核心特性

1. 组合式 API

组合式 API 是 Vue 3 最重要的新特性之一,它允许我们根据逻辑相关性组织代码,而不是根据选项类型。

setup 语法糖
<template>
  <div>
    <p>姓名: {{ user.name }}</p>
    <p>年龄: {{ user.age }}</p>
    <button @click="updateUser">更新用户信息</button>
  </div>
</template>

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

// 响应式对象
const user = reactive({
  name: '张三',
  age: 30
})

// 方法
function updateUser() {
  user.name = '李四'
  user.age = 25
}
</script>
ref vs reactive
<template>
  <div>
    <h2>ref 示例</h2>
    <p>计数器: {{ count }}</p>
    <button @click="increment">增加</button>
    
    <h2>reactive 示例</h2>
    <p>用户: {{ user.name }}, 年龄: {{ user.age }}</p>
    <button @click="updateUser">更新</button>
  </div>
</template>

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

// 基本类型响应式
const count = ref(0)
function increment() {
  count.value++ // ref 需要通过 .value 访问
}

// 对象类型响应式
const user = reactive({
  name: 'Vue',
  age: 3
})
function updateUser() {
  user.name = 'Vue 3' // reactive 直接修改属性
  user.age = 4
}
</script>

2. 计算属性与监听

computed
<template>
  <div>
    <p>原始价格: {{ price }}</p>
    <p>折扣价格: {{ discountedPrice }}</p>
    <input v-model="discount" type="number" placeholder="折扣率(%)">
  </div>
</template>

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

const price = ref(100)
const discount = ref(10)

// 计算属性 - 依赖变化时自动重新计算
const discountedPrice = computed(() => {
  return price.value * (1 - discount.value / 100)
})
</script>
watch
<template>
  <div>
    <input v-model="query" placeholder="搜索内容">
    <p>搜索结果: {{ results.length }} 条</p>
  </div>
</template>

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

const query = ref('')
const results = ref([])

// 监听 query 变化
watch(query, (newQuery, oldQuery) => {
  console.log(`搜索从 "${oldQuery}" 变为 "${newQuery}"`)
  if (newQuery) {
    fetchResults(newQuery)
  } else {
    results.value = []
  }
})

async function fetchResults(query) {
  // 模拟 API 请求
  await new Promise(resolve => setTimeout(resolve, 500))
  results.value = [
    { id: 1, title: `结果 1: ${query}` },
    { id: 2, title: `结果 2: ${query}` }
  ]
}
</script>

3. 生命周期钩子

<template>
  <div>
    <p>生命周期演示</p>
    <button @click="destroy">销毁组件</button>
  </div>
</template>

<script setup>
import { onMounted, onUpdated, onUnmounted, ref } from 'vue'

const count = ref(0)

// 组件挂载后
onMounted(() => {
  console.log('组件已挂载')
  // 可以在这里初始化数据、绑定事件等
})

// 组件更新后
onUpdated(() => {
  console.log('组件已更新')
  // 可以在这里处理 DOM 更新后的逻辑
})

// 组件卸载前
onUnmounted(() => {
  console.log('组件将卸载')
  // 可以在这里清理定时器、事件监听器等
})

function destroy() {
  // 销毁组件的逻辑
}
</script>

4. 组件通信

Props
<!-- ParentComponent.vue -->
<template>
  <div>
    <ChildComponent :message="parentMessage" :count="parentCount" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentMessage = ref('Hello from parent')
const parentCount = ref(10)
</script>

<!-- ChildComponent.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ count }}</p>
  </div>
</template>

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

// 定义接收的 props
const props = defineProps({
  message: String,
  count: Number
})
</script>
Emits
<!-- ChildComponent.vue -->
<template>
  <div>
    <button @click="handleClick">点击我</button>
  </div>
</template>

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

// 定义可以触发的事件
const emit = defineEmits(['click', 'customEvent'])

function handleClick() {
  // 触发事件并传递数据
  emit('click', '按钮被点击了')
  emit('customEvent', { id: 1, timestamp: Date.now() })
}
</script>

<!-- ParentComponent.vue -->
<template>
  <div>
    <ChildComponent @click="onChildClick" @customEvent="onCustomEvent" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

function onChildClick(message) {
  console.log('子组件事件:', message)
}

function onCustomEvent(data) {
  console.log('自定义事件数据:', data)
}
</script>

三、高级特性

1. 自定义 Hook

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

export function useCounter(initialValue = 0, step = 1) {
  const count = ref(initialValue)
  
  // 计算属性
  const isEven = computed(() => count.value % 2 === 0)
  const isOdd = computed(() => count.value % 2 !== 0)
  
  // 方法
  function increment() {
    count.value += step
  }
  
  function decrement() {
    count.value -= step
  }
  
  function reset() {
    count.value = initialValue
  }
  
  return {
    count,
    isEven,
    isOdd,
    increment,
    decrement,
    reset
  }
}

使用自定义 Hook:

<template>
  <div>
    <h2>计数器: {{ count }}</h2>
    <p>是否为偶数: {{ isEven ? '是' : '否' }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script setup>
import { useCounter } from './composables/useCounter'

// 使用自定义 Hook
const { count, isEven, increment, decrement, reset } = useCounter(10, 2)
</script>

2. 组件插槽

基础插槽
<!-- Button.vue -->
<template>
  <button class="custom-button">
    <!-- 插槽位置 -->
    <slot></slot>
  </button>
</template>

<style scoped>
.custom-button {
  padding: 8px 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

<!-- 使用 Button 组件 -->
<template>
  <Button>
    <span>点击我</span> <!-- 插槽内容 -->
  </Button>
</template>

<script setup>
import Button from './Button.vue'
</script>
具名插槽
<!-- Card.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header"></slot>
    </div>
    <div class="card-body">
      <slot></slot> <!-- 默认插槽 -->
    </div>
    <div class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 16px;
  margin: 16px;
}

.card-header {
  font-weight: bold;
  margin-bottom: 8px;
}

.card-footer {
  margin-top: 8px;
  text-align: right;
}
</style>

<!-- 使用 Card 组件 -->
<template>
  <Card>
    <template #header>
      <h3>卡片标题</h3>
    </template>
    
    <p>这是卡片的主要内容...</p>
    <p>可以包含多行文本</p>
    
    <template #footer>
      <button>确定</button>
      <button>取消</button>
    </template>
  </Card>
</template>

<script setup>
import Card from './Card.vue'
</script>

3. Teleport

Teleport 允许我们将组件的一部分渲染到 DOM 树中的另一个位置,非常适合模态框、通知等场景。

<template>
  <div>
    <button @click="showModal = true">打开模态框</button>
    
    <Teleport to="body">
      <div v-if="showModal" class="modal-overlay" @click="showModal = false">
        <div class="modal-content" @click.stop>
          <h2>模态框标题</h2>
          <p>这是模态框内容</p>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

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

const showModal = ref(false)
</script>

<style>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal-content {
  background-color: white;
  padding: 20px;
  border-radius: 4px;
  width: 80%;
  max-width: 500px;
}
</style>

四、性能优化

1. 编译时优化

Vue 3 在编译阶段进行了多项优化:

  • Tree-shaking 支持:只打包实际使用的功能
  • Patch Flag:标记模板中变化的部分,减少 DOM 比对
  • 静态提升:将静态内容提升到渲染函数外部,避免重复创建

2. 运行时优化

v-memo
<template>
  <div>
    <div v-for="item in items" :key="item.id" v-memo="[item.id, item.visible]">
      <p>{{ item.name }}</p>
      <p>{{ item.description }}</p>
      <!-- 只有当 item.id 或 item.visible 变化时,才会重新渲染 -->
    </div>
  </div>
</template>
v-once
<template>
  <div>
    <h1 v-once>{{ staticTitle }}</h1>
    <!-- 这个标题只会渲染一次,之后不会再更新 -->
    <p>{{ dynamicContent }}</p>
    <!-- 这个内容会正常更新 -->
  </div>
</template>

3. 按需导入

// main.js
import { createApp } from 'vue'
// 按需导入组件
import { Button, Input } from 'element-plus'
import App from './App.vue'

const app = createApp(App)

// 注册需要的组件
app.use(Button)
app.use(Input)

app.mount('#app')

五、状态管理

Pinia 基础

Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者。

安装
npm install pinia
配置
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')
创建 Store
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // 状态
  state: () => ({
    count: 0,
    name: 'Vue 3'
  }),
  
  // 计算属性
  getters: {
    doubleCount: (state) => state.count * 2,
    isEven: (state) => state.count % 2 === 0
  },
  
  // 方法
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    reset() {
      this.count = 0
    }
  }
})
使用 Store
<template>
  <div>
    <h2>计数: {{ counterStore.count }}</h2>
    <h3>双倍计数: {{ counterStore.doubleCount }}</h3>
    <p>是否为偶数: {{ counterStore.isEven ? '是' : '否' }}</p>
    
    <button @click="counterStore.increment">增加</button>
    <button @click="counterStore.decrement">减少</button>
    <button @click="counterStore.reset">重置</button>
  </div>
</template>

<script setup>
import { useCounterStore } from './stores/counter'

// 使用 store
const counterStore = useCounterStore()
</script>

六、路由

Vue Router 4 基础

安装
npm install vue-router@4
配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)
app.mount('#app')
使用路由
<!-- App.vue -->
<template>
  <div>
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于我们</router-link>
    </nav>
    
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>

<style>
nav {
  padding: 20px;
  background-color: #f5f5f5;
}

router-link {
  margin-right: 10px;
  text-decoration: none;
  color: #42b983;
}

router-link.router-link-active {
  font-weight: bold;
}
</style>

七、最佳实践

1. 代码组织

  • 使用 composables 目录存放可复用的自定义 Hook
  • 按功能模块组织组件
  • 合理使用 utils 目录存放工具函数

2. TypeScript 支持

Vue 3 对 TypeScript 有很好的支持:

<template>
  <div>
    <p>{{ user.name }}</p>
    <p>{{ user.age }}</p>
  </div>
</template>

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

interface User {
  name: string
  age: number
  email?: string
}

const user = ref<User>({
  name: '张三',
  age: 30
})
</script>

3. 测试策略

  • 使用 Vitest 进行单元测试
  • 使用 Cypress 进行端到端测试
  • 编写组件测试验证组件行为

八、总结

Vue 3 为前端开发带来了许多强大的新特性和性能改进,从组合式 API 到更好的 TypeScript 支持,从更小的打包体积到更快的渲染速度。通过本文的学习,你应该已经掌握了 Vue 3 的核心概念和高级技巧,可以开始构建现代化的 Vue 3 应用了!

记住,实践是最好的学习方式。不断尝试新的特性,探索不同的应用场景,你会发现 Vue 3 的无限可能!

Happy Vue 3 Coding!

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值