vue3

Vue3 学习笔记


第 1 章 Vue3 入门与基础

1.1 Vue3 简介

  • 发布时间:2020 年 9 月正式发布,代号 One Piece
  • 核心重构:基于 ES6+ 重写,采用 Composition API、Vite 构建、响应式系统重构(Proxy 替代 Object.defineProperty)
  • 特点:性能提升(体积减小 40%、渲染快 55%)、更好的 TypeScript 支持、组合式 API、按需编译
  • 兼容性:不支持 IE11 及以下,支持现代浏览器
  • 应用场景:单页面应用(SPA)、移动端 / H5、后台管理系统、跨端应用(UniApp/TSX)

1.2 环境与开发工具

  • 构建工具:

    • Vite:Vue 官方推荐,极速冷启动、按需编译(开发首选)
    • Vue CLI:基于 Webpack,兼容老项目,配置繁琐
  • 开发工具(IDE):

    • VSCode:轻量免费,必装插件:Volar(Vue3 专属)、TypeScript Vue Plugin
    • WebStorm:功能全,对 Vue/TS 支持好,付费
  • 项目初始化:

# Vite 初始化(推荐)
npm create vite@latest my-vue3-project -- --template vue
cd my-vue3-project
npm install
npm run dev

# Vue CLI 初始化(兼容老项目)
npm install -g @vue/cli
vue create my-vue3-project
cd my-vue3-project
npm run serve

1.3 基础语法

  • 模板语法:插值表达式 {{ }}、指令(v-xxx)、属性绑定 v-bind/:、事件绑定 v-on/@

  • 注释:模板中 <!-- 注释 -->,脚本中 ////* */

  • 核心规则:

    • 模板只能有一个根节点(Vue3 支持多根,但推荐单根)
    • 指令后缀 .modifier(如 @click.stop 阻止冒泡)
    • 插值表达式支持简单表达式,不支持语句(如 if/for
  • ⚠️ 常见语法坑:

    • 混淆 v-bind(绑定属性)和 v-model(双向绑定)
    • 忘记 v-forkey 导致渲染异常
    • 模板中使用未在 setup 中暴露的变量

第 2 章 核心概念

2.1 组合式 API(Composition API)

  • 核心优势:逻辑复用、类型友好、代码组织更灵活(替代 Vue2 Options API)

  • 入口函数:setup(),组件创建前执行,无 this 指向

  • 执行时机:在 beforeCreate 之前执行,此时组件实例未创建

  • 两种写法:

    <!-- 选项式(Options API,兼容写法) -->
    <script>
    export default {
      data() {
        return { count: 0 }
      },
      methods: {
        increment() { this.count++ }
      }
    }
    </script>
    
    <!-- 组合式(Composition API,推荐) -->
    <script setup>
    import { ref } from 'vue'
    const count = ref(0)
    const increment = () => { count.value++ }
    </script>
    

2.2 响应式系统

  • 核心原理:基于 ES6 Proxy 实现,支持数组、对象、Map/Set 等所有数据类型

  • 响应式 API 分类:

    | API | 用途 | 特点 |

| ————— | —————- | ——————————————- |
| ref() | 基本类型响应式 | 需通过 .value 访问 / 修改,模板中自动解包 |
| reactive() | 引用类型响应式 | 直接访问属性,不支持基本类型 |
| computed() | 计算属性 | 缓存结果,依赖变化才重新计算 |
| watch() | 监听数据变化 | 支持深度监听、立即执行、停止监听 |
| watchEffect() | 自动监听依赖变化 | 立即执行,无需指定监听源 |

  • 基础示例:
<script setup>
import { ref, reactive, computed, watch, watchEffect } from 'vue'

// 基本类型响应式
const num = ref(10)
num.value = 20 // 修改

// 引用类型响应式
const user = reactive({ name: '张三', age: 20 })
user.age = 21 // 修改

// 计算属性
const doubleNum = computed(() => num.value * 2)

// 监听
watch(num, (newVal, oldVal) => {
  console.log(`num从${oldVal}变为${newVal}`)
}, { immediate: true, deep: true })

// 自动监听
const stop = watchEffect(() => {
  console.log(`自动监听:${num.value}`)
})
// 停止监听
// stop()
</script>
  • ⚠️ 响应式坑点:

    • reactive 赋值新对象会失去响应式(解决:用 ref 包裹对象 / Object.assign
    • refreactive 中会自动解包(无需 .value
    • 数组索引赋值 / 修改长度不会触发响应式(解决:用 push/spliceref 包裹数组)

2.3 模板指令

  • 核心指令:

    指令 用途 示例
    v-text 文本绑定 <div v-text="name"></div>
    v-html 富文本绑定(慎用 XSS) <div v-html="htmlStr"></div>
    v-bind/: 属性绑定 <img :src="imgUrl">
    v-on/@ 事件绑定 <button @click="handleClick">
    v-model 双向绑定 <input v-model="username">
    v-for 列表渲染 <li v-for="item in list" :key="item.id">{{item.name}}</li>
    v-if/v-else 条件渲染(销毁 / 创建) <div v-if="flag">显示</div>
    v-show 条件显示(隐藏 / 显示) <div v-show="flag">显示</div>
    v-slot/# 插槽 <slot name="footer"></slot>
  • v-model 修饰符:

    ```




- `v-for` 注意事项:

- 必须加唯一 `key`(推荐用 id,避免用 index)
- 避免和 `v-if` 同节点(v-for 优先级更高,性能差)



------

## 第 3 章 组件化开发

### 3.1 组件基础

- 组件类型:

- 全局组件:`app.component('MyComponent', { ... })`
- 局部组件:单文件组件(.vue),导入后使用

- 单文件组件(SFC)结构:

### 3.2 组件通信

| 通信方式       | 适用场景                | 实现方式                                        |
| -------------- | ----------------------- | ----------------------------------------------- |
| Props          | 父 → 子                 | 子组件 `defineProps`,父组件属性传递            |
| Emits          | 子 → 父                 | 子组件 `defineEmits`,父组件事件监听            |
| v-model        | 父子双向绑定            | 子组件 `defineModel`(Vue3.4+)/ 自定义 v-model |
| provide/inject | 跨层级(爷 → 孙)       | 祖先 `provide` 提供,后代 `inject` 注入         |
| Pinia/Vuex     | 全局状态共享            | 全局仓库存储,任意组件访问                      |
| 模板引用       | 父访问子组件方法 / 属性 | `ref` 绑定组件,`defineExpose` 暴露内容         |

- 核心示例:
```

3.3 插槽(Slot)

  • 作用:组件内容分发,增强组件灵活性

  • 分类:

    • 默认插槽:无名称插槽
    • 具名插槽:命名插槽,精准分发
    • 作用域插槽:子传父数据,父自定义渲染
<!-- 子组件 SlotDemo.vue -->
<template>
  <div>
    <!-- 默认插槽 -->
    <slot></slot>
    <!-- 具名插槽 -->
    <slot name="footer"></slot>
    <!-- 作用域插槽 -->
    <slot name="item" :list="list"></slot>
  </div>
</template>

<script setup>
const list = ref([1,2,3])
</script>

<!-- 父组件使用 -->
<template>
  <SlotDemo>
    <!-- 默认插槽内容 -->
    <div>默认插槽内容</div>
    <!-- 具名插槽 -->
    <template #footer>
      <div>页脚插槽内容</div>
    </template>
    <!-- 作用域插槽 -->
    <template #item="slotProps">
      <div v-for="item in slotProps.list" :key="item">{{item}}</div>
    </template>
  </SlotDemo>
</template>

3.4 异步组件

  • 作用:按需加载组件,减小首屏体积
  • 实现:defineAsyncComponent
<script setup>
import { defineAsyncComponent } from 'vue'

// 异步加载组件
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./AsyncComponent.vue'),
  loadingComponent: () => import('./Loading.vue'), // 加载中组件
  errorComponent: () => import('./Error.vue'), // 加载失败组件
  delay: 200, // 延迟显示加载组件(ms)
  timeout: 3000 // 超时时间
})
</script>

<template>
  <AsyncComponent />
</template>

第 4 章 路由(Vue Router)

4.1 路由基础

  • 版本:Vue3 对应 Vue Router 4.x
  • 核心作用:SPA 页面跳转、路由守卫、参数传递
  • 安装与配置:
npm install vue-router@4
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // 懒加载路由
    component: () => import('../views/About.vue')
  },
  {
    path: '/user/:id', // 动态路由
    name: 'User',
    component: () => import('../views/User.vue'),
    props: true // 开启props传参
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // Vite 环境变量
  routes
})

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

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

4.2 路由使用

  • 声明式导航:
<template>
  <!-- 基础跳转 -->
  <router-link to="/">首页</router-link>
  <!-- 命名路由跳转 -->
  <router-link :to="{ name: 'User', params: { id: 1 } }">用户1</router-link>
  <!-- 查询参数跳转 -->
  <router-link :to="{ path: '/about', query: { page: 1 } }">关于</router-link>
  
  <!-- 路由出口 -->
  <router-view />
</template>
  • 编程式导航:
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter() // 路由实例
const route = useRoute() // 当前路由信息

// 跳转页面
const goToUser = () => {
  router.push({ name: 'User', params: { id: 2 } })
}

// 替换页面(无历史记录)
const replaceToHome = () => {
  router.replace('/')
}

// 前进/后退
const goBack = () => {
  router.go(-1) // 后退一步
}

// 获取路由参数
console.log(route.params.id) // 动态参数
console.log(route.query.page) // 查询参数
</script>

4.3 路由守卫

  • 作用:路由跳转拦截(权限校验、登录验证)

  • 分类:

    • 全局守卫:router.beforeEachrouter.afterEach
    • 路由独享守卫:beforeEnter
    • 组件内守卫:onBeforeRouteEnteronBeforeRouteUpdate
// 全局前置守卫
router.beforeEach((to, from, next) => {
  // 验证登录状态
  const isLogin = localStorage.getItem('token')
  if (to.path === '/admin' && !isLogin) {
    next('/login') // 未登录跳登录页
  } else {
    next() // 放行
  }
})

// 组件内守卫
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate } from 'vue-router'

// 进入路由前(无this,用next传参)
onBeforeRouteEnter((to, from, next) => {
  next(vm => {
    // vm 是组件实例
    console.log('进入路由', vm)
  })
})

// 路由参数更新时(如 /user/1 → /user/2)
onBeforeRouteUpdate((to, from) => {
  console.log('路由参数更新', to.params.id)
})
</script>

第 5 章 状态管理(Pinia)

5.1 Pinia 简介

  • 替代 Vuex,Vue 官方推荐,专为 Vue3 设计
  • 特点:无 mutations、支持 TS、模块化、轻量化、支持插件
  • 安装:
npm install pinia

5.2 核心使用

// src/store/index.js
import { createPinia } from 'pinia'

export const pinia = createPinia()

// src/store/user.js
import { defineStore } from 'pinia'

// 定义仓库(id 唯一)
export const useUserStore = defineStore('user', {
  // 状态(替代 state)
  state: () => ({
    name: '张三',
    age: 20,
    token: ''
  }),
  // 计算属性(替代 getters)
  getters: {
    doubleAge: (state) => state.age * 2,
    // 访问其他getter
    fullInfo: (state) => `${state.name} - ${state.doubleAge}`
  },
  // 方法(替代 actions,支持同步/异步)
  actions: {
    updateName(newName) {
      this.name = newName // 直接修改state
    },
    async login(userInfo) {
      // 异步请求
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(userInfo)
      })
      const data = await res.json()
      this.token = data.token // 保存token
    },
    // 批量修改state
    resetState() {
      this.$reset() // 重置为初始状态
    }
  }
})
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { pinia } from './store'

createApp(App).use(pinia).mount('#app')
<!-- 组件中使用 -->
<script setup>
import { useUserStore } from '@/store/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 方式1:直接访问(响应式)
console.log(userStore.name)
userStore.updateName('李四') // 调用action

// 方式2:解构(需用storeToRefs保持响应式)
const { name, doubleAge } = storeToRefs(userStore)
const { login } = userStore // 方法无需解构

// 批量修改state
const updateState = () => {
  userStore.$patch({
    age: 25,
    name: '王五'
  })
  // 或函数式(适合复杂修改)
  userStore.$patch((state) => {
    state.age += 1
  })
}
</script>

5.3 模块化

  • 按业务拆分仓库(user.js、cart.js、setting.js)
  • 仓库间相互调用:
// src/store/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  state: () => ({
    list: []
  }),
  actions: {
    addItem(item) {
      const userStore = useUserStore()
      if (userStore.token) { // 访问其他仓库
        this.list.push(item)
      }
    }
  }
})

第 6 章 生命周期

6.1 生命周期钩子

  • Vue3 组合式 API 钩子(替代 Vue2 选项式钩子):

    | 组合式 API | 选项式 API | 执行时机 |

    | :—————- | :————— | :————————— |

    | onMounted | mounted | 组件挂载完成 |

    | onUpdated | updated | 组件更新完成 |

    | onUnmounted | unmounted | 组件卸载完成 |

    | onBeforeMount | beforeMount | 组件挂载前 |

    | onBeforeUpdate | beforeUpdate | 组件更新前 |

    | onBeforeUnmount | beforeUnmount | 组件卸载前 |

    | onErrorCaptured | errorCaptured | 捕获子组件错误 |

    | onActivated | activated | 缓存组件激活时(keep-alive) |

    | onDeactivated | deactivated | 缓存组件失活时(keep-alive) |

  • 使用示例:

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

// 挂载完成(常用:DOM操作、请求数据)
onMounted(() => {
  console.log('组件挂载完成')
  fetchData()
})

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

// 卸载前(常用:清除定时器、取消监听)
onBeforeUnmount(() => {
  clearInterval(timer)
})

// 卸载完成
onUnmounted(() => {
  console.log('组件卸载完成')
})

const fetchData = async () => {
  const res = await fetch('/api/data')
  const data = await res.json()
  console.log(data)
}
</script>

第 7 章 高级特性

7.1 自定义指令

  • 作用:扩展模板语法,封装 DOM 操作
  • 定义方式:
<!-- 局部指令 -->
<script setup>
// 自定义指令 v-focus
const vFocus = {
  mounted(el) {
    el.focus() // 挂载后聚焦
  }
}

// 简化写法(仅mounted)
// const vFocus = (el) => el.focus()
</script>

<template>
  <input v-focus />
</template>
// 全局指令(main.js)
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 注册全局指令 v-loading
app.directive('loading', {
  mounted(el, binding) {
    if (binding.value) {
      el.innerHTML = '<div class="loading">加载中...</div>'
    } else {
      el.innerHTML = ''
    }
  },
  updated(el, binding) {
    // 更新时触发
  }
})

app.mount('#app')

7.2 组合式函数(Composables)

  • 作用:逻辑复用(替代 Vue2 mixins)
  • 规范:文件名以 use 开头,返回响应式数据 / 方法
// src/composables/useFetch.js
import { ref, watchEffect } from 'vue'

export const useFetch = (url) => {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)

  const fetchData = async () => {
    loading.value = true
    try {
      const res = await fetch(url)
      data.value = await res.json()
      error.value = null
    } catch (e) {
      error.value = e.message
      data.value = null
    } finally {
      loading.value = false
    }
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, loading, error, fetchData }
}
<!-- 组件中使用 -->
<script setup>
import { useFetch } from '@/composables/useFetch'

const { data, loading, error } = useFetch('/api/list')
</script>

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">{{ error }}</div>
  <div v-else>{{ data }}</div>
</template>

7.3 Teleport(传送门)

  • 作用:将组件 DOM 挂载到指定节点(如弹窗、提示框)
<template>
  <button @click="showModal = true">打开弹窗</button>
  
  <teleport to="body">
    <div class="modal" v-if="showModal">
      <div class="modal-content">
        <h3>弹窗内容</h3>
        <button @click="showModal = false">关闭</button>
      </div>
    </div>
  </teleport>
</template>

<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>

<style scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-content {
  background: white;
  padding: 20px;
  border-radius: 4px;
}
</style>

第 8 章 工程化与实战

8.1 Vite 配置

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  // 路径别名
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  // 开发服务器
  server: {
    port: 3000,
    open: true,
    proxy: {
      // 接口代理
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  // 构建配置
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    minify: 'terser'
  }
})

8.2 样式处理

  • CSS 预处理器(SCSS/LESS):
npm install sass -D
<style scoped lang="scss">
$primary-color: #42b983;
.hello {
  color: $primary-color;
}
</style>
  • CSS Modules:
<style module>
.red {
  color: red;
}
</style>

<template>
  <div :class="$style.red">红色文字</div>
</template>

8.3 生产环境优化

  1. 路由懒加载(已在路由章节说明)
  2. 组件异步加载(已在组件章节说明)
  3. 图片优化:使用 vite-plugin-imagemin 压缩图片
  4. 体积分析:rollup-plugin-visualizer 分析打包体积
  5. 按需引入:UI 库(如 Element Plus)按需引入

8.4 常见问题排查

  • 响应式丢失:检查 reactive 赋值新对象、数组索引修改
  • 模板报错:检查变量是否暴露、指令语法是否正确
  • 路由 404:Vite 配置 base、Nginx 配置 try_files $uri $uri/ /index.html
  • TS 类型错误:检查 defineProps/defineEmits 类型定义

附录 :常用 API 速查

分类 核心 API 用途
响应式 ref、reactive、computed、watch 数据响应式处理
生命周期 onMounted、onUpdated、onUnmounted 组件生命周期管理
组件通信 defineProps、defineEmits、defineExpose 父子组件通信
路由 useRouter、useRoute、createRouter 路由跳转、参数获取
状态管理 defineStore、storeToRefs Pinia 仓库定义、使用
工具函数 nextTick、provide、inject 异步更新、跨层级通信
高级特性 defineAsyncComponent、createVNode 异步组件、手动创建 VNode

附录 :避坑清单

  1. 响应式:reactive 赋值新对象失去响应式,用 Object.assignref 包裹
  2. 模板:v-for 必须加唯一 key,避免和 v-if 同节点
  3. 组合式 API:setup 中无 this,用 useRouter/useRoute 替代
  4. 样式:scoped 样式不生效时,检查 ::v-deep 穿透(::v-deep .class
  5. 路由:动态路由参数更新需监听 onBeforeRouteUpdate
  6. Pinia:解构 state 需用 storeToRefs 保持响应式
  7. 打包:Vite 打包路径问题,配置 base 或 Nginx 转发
  8. 自定义指令:钩子函数参数 el 是原生 DOM,不是 Vue 实例