vue需要学习的点
对MVVM的理解
MVVM 是 Model-View-ViewModel 的缩写
- Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑。
- View 代表 UI 组件, 它负责将数据模型转化成 UI 展现出来。
- ViewModel 监听模型数据的改变和控制视图行为 、处理用户交互, 简单理解就是⼀个同步View 和 Model 的对象, 连接 Model 和 View
这种架构下,开发者只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题, 复杂的数据状态维护完全由 MVVM 来统⼀管理
常用vue命令
| 指令 | 作用说明 | 典型用法示例 |
|---|---|---|
| v-text | 将元素的文本内容替换为表达式的值(替代 {{}}) | <p v-text="msg"></p> |
| v-html | 将元素的 innerHTML 更新为表达式的内容(会有 XSS 风险) | <div v-html="rawHtml"></div> |
| v-show | 根据表达式的真假切换元素的 display CSS 属性实现显示隐藏 | <div v-show="isVisible"></div> |
| v-if | 条件渲染,表达式为真时渲染该元素,假时销毁元素(不占 DOM) | <p v-if="show">显示内容</p> |
| v-else | v-if 的反向条件,必须紧接 v-if 或 v-else-if 之后 | <p v-else>隐藏内容</p> |
| v-else-if | v-if 的“else if”分支,用于链式条件判断 | <p v-else-if="otherCondition">内容</p> |
| v-for | 列表渲染,用于遍历数组或对象生成一组元素 | <li v-for="item in items" :key="item.id">{{ item.text }}</li> |
| v-bind | 动态绑定 HTML 属性或组件 prop,简写为 : | <img :src="imgSrc"> |
| v-on | 监听 DOM 事件或组件自定义事件,简写为 @ | <button @click="handleClick">点我</button> |
| v-model | 双向数据绑定,通常用于表单控件(input、select、textarea 等) | <input v-model="username"> |
| v-pre | 跳过该元素和子元素的编译,进行原样输出(提高性能,调试时用) | <span v-pre>{{ rawMustache }}</span> |
| v-cloak | 保证 Vue 编译完成之后再显示元素,避免闪烁(通常配合 CSS 使用) | <div v-cloak>内容</div> |
| v-once | 元素和组件只渲染一次,之后的更新不会渲染该元素 | <span v-once>{{ message }}</span> |
如何自定义vue命令
| 项目 | Vue 2 | Vue 3 |
|---|---|---|
| 注册方式 | Vue.directive() / 组件内 directives | app.directive() / 组件内 directives |
| 生命周期钩子名称 | bind, inserted, update, componentUpdated, unbind | created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted |
| 生命周期更多钩子 | 较少,只匹配关键阶段 | 更细粒度,更符合组件生命周期 |
| 指令 API 对象格式 | vs Vue 3 类似,只是钩子不同 | 同 Vue 2,但钩子名称改进 |
| 参数和修饰符 | 支持 binding.value, binding.arg, binding.modifiers | 同 Vue 2 |
vue的生命周期
1. Vue 2 生命周期钩子
Vue 2 的生命周期可以分为四个阶段:
| 阶段 | 钩子函数 | 说明 |
|---|---|---|
| 创建阶段 | beforeCreate | 实例初始化时调用,数据观测和事件未初始化 |
created | 实例已创建,数据观测和事件已初始化 | |
| 挂载阶段 | beforeMount | 挂载开始之前,相关 DOM 还未渲染 |
mounted | 挂载完成,DOM 已插入页面 | |
| 更新阶段 | beforeUpdate | 数据更新后,DOM 重新渲染前调用 |
updated | 组件 DOM 重新渲染并更新后调用 | |
| 销毁阶段 | beforeDestroy | 实例销毁前调用 |
destroyed | 实例销毁完成,解绑所有事件,清理资源 |
2. Vue 3 生命周期钩子
Vue 3 基于组合式 API 升级,仍保留选项式生命周期,同时对生命周期做了更多补充。整体钩子和 Vue 2 类似,具体列表:
| 阶段 | 钩子函数 | 说明 |
|---|---|---|
| 创建阶段 | beforeCreate | Vue 3 依然支持,但在组合式 API 不常用 |
created | 同上 | |
| 挂载阶段 | beforeMount | 挂载开始之前 |
mounted | 挂载完成 | |
| 更新阶段 | beforeUpdate | 数据更新前,DOM 渲染之前调用 |
updated | DOM 更新完成后调用 | |
| 卸载阶段 | beforeUnmount | 实例卸载前调用(Vue 3 新增) |
unmounted | 实例卸载后调用(Vue 3 新增) |
3. Vue 3 组合式 API 中的生命周期钩子
Vue 3 新增了组合式 API(setup 函数),通过如下函数注册生命周期钩子:
| 生命周期阶段 | 组合式 API 钩子函数 | 说明 |
|---|---|---|
| 创建 | onBeforeMount(() => {}) | 挂载之前 |
onMounted(() => {}) | 挂载完成 | |
| 更新 | onBeforeUpdate(() => {}) | 更新之前 |
onUpdated(() => {}) | 更新完成 | |
| 卸载 | onBeforeUnmount(() => {}) | 卸载之前 |
onUnmounted(() => {}) | 卸载完成 | |
| 激活和停用 | onActivated(() => {}) | keep-alive 组件激活 |
onDeactivated(() => {}) | keep-alive 组件停用 |
vue的响应式原理
1.vue2
核心是依赖收集 + 发布订阅模式,使用 Object.defineProperty 对对象的属性进行 getter/setter 拦截。
function defineReactive(obj, key, val) {const dep = new Dep()Object.defineProperty(obj, key, {get() {if (Dep.target) { // Dep.target 指当前扫描的 watcherdep.addDep(Dep.target)}return val},set(newVal) {if (newVal !== val) {val = newValdep.notify() // 通知所有 watcher 更新}}})
}限制和缺点:
- 只能监听对象已有属性,新增新属性不会自动响应式(需要用 Vue.set)
- 数组的变化通过变异方法(push、pop等)重写实现响应式
- 只能监听到对象的一级属性,深层嵌套需要递归监听
2.vue3
基于 ES6 的 Proxy 实现响应式。代理整个对象,拦截所有属性的访问/修改/删除,不需要递归每个子属性。原理:对目标对象做代理,所有访问通过 get、set 拦截完成。
function reactive(target) {return new Proxy(target, {get(target, key, receiver) {// 依赖收集track(target, key)return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver)// 触发依赖trigger(target, key)return result}})
}3.总结
| 方面 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 技术实现 | 对对象属性逐个用 defineProperty 拦截 | 用 Proxy 包裹整个对象进行拦截 |
| 支持新增/删除属性 | 不支持,需要用 Vue.set | 天生支持 |
| 数组响应式 | 通过重写数组变异方法实现 | 数组作为对象代理自然支持 |
| 依赖收集粒度 | 只能对属性做依赖收集 | 基于操作符拦截,更精准,按需追踪 |
| 性能 | 需要递归遍历,性能压力大 | 性能更好,按需依赖追踪 |
| 实现复杂度 | 需要递归遍历、手动处理数组 | 简洁,统一代理所有操作 |
vue的diff算法
Vue 基于虚拟 DOM 实现渲染,每次状态变化产生新 VNode 树,会通过 Diff 算法比较新旧 VNode 树的差异,根据差异调用最少的 DOM 操作,实现 DOM 最小化更新。
| 处理方式 | 意义 | 操作 | 指针变化 |
|---|---|---|---|
| 旧前 vs 新前 | 顺序未变,头部对比 | patch,指针+1 | oldStart++, newStart++ |
| 旧后 vs 新后 | 顺序未变,尾部对比 | patch,指针-1 | oldEnd--, newEnd-- |
| 旧后 vs 新前 | 右移节点至左 | patch + 移动旧尾节点 | oldEnd--, newStart++ |
| 旧前 vs 新后 | 左移节点至右 | patch + 移动旧前节点 | oldStart++, newEnd-- |
| 其它(key查找) | 复用已有节点或新增节点 | 移动或插入节点 | 根据具体操作调整指针 |
| 新节点剩余 | 新增节点 | 创建新节点 | newStart++ 或 newEnd-- |
| 旧节点剩余 | 删除多余节点 | 删除节点 | oldStart++ 或 oldEnd-- |

vue的Optional API 和Composition API
1. Options API(选项式 API)
这是 Vue 2 以及 Vue 3 依然支持的传统写法,开发者通过预定义的组件选项(Options)来组织代码,比如 data、methods、computed、watch、props 等。
特点:
- 代码组织结构清晰,基于 Vue 约定的选项划分功能。
- 易于初学者理解,适合小中型项目。
- 组件逻辑往往按功能划分,但可能导致同一功能分散在多个选项内,随着组件变复杂难以维护。
示例代码
export default {data() {return {count: 0}},computed: {doubled() {return this.count * 2}},methods: {increment() {this.count++}},watch: {count(newVal, oldVal) {console.log('count changed:', newVal)}}
}2. Composition API(组合式 API)
Vue 3 引入的新方式,开发者通过在 setup() 函数内使用函数调用和响应式 API(如 ref、reactive、computed、watch 等)组合逻辑代码。
特点:
- 逻辑复用和组合更灵活:可以把相关代码片段封装成可复用的函数(composables)。
- 更适合大型项目和复杂组件,避免 Options API 中不同选项间逻辑分散问题。
- 代码布局更灵活,方便按业务功能组织代码而非选项划分。
- 需要了解一定的响应式原理,对初学者有一定门槛。
import { ref, computed, watch } from 'vue'export default {setup() {const count = ref(0)const doubled = computed(() => count.value * 2)function increment() {count.value++}watch(count, (newVal, oldVal) => {console.log('count changed:', newVal)})return {count,doubled,increment}}
}vue中的逻辑复用
1. Options API 的逻辑复用方式及局限
主要手段
- mixins(混入)
- extends(继承)
这些机制允许把一段逻辑写在一个 mixin 对象或基类组件中,然后被多个组件复用。
// mixin.js
export const myMixin = {data() {return {count: 0}},methods: {increment() {this.count++}}
}
import { myMixin } from './mixin'export default {mixins: [myMixin],mounted() {this.increment()}
}局限和问题
命名冲突:不同 mixin 可能有相同的 data、methods、computed 名称,产生冲突难以管理。
隐式依赖和状态来源混乱:mixin 里面有状态和方法,组件使用后状态来自何处不直观,增加理解和维护成本。
逻辑难以拆分和组合:mixin 是扁平的配置,无法像函数那样灵活接收参数、返回结果、依赖注入。
追踪调用关系困难:逻辑分散在多个 mixin,调试时难以跟踪和维护。
扩展继承复杂度:使用
extends形式继承 父组件逻辑时,继承链越长越复杂,状态管理困难。
2.Composition API 的逻辑复用 —— Composable 函数
核心思想
- 将一块响应式逻辑封装到一个普通函数中(称为 composable 函数)
- 函数内部使用
ref、reactive、computed、watch等 API - 通过返回对象暴露响应式数据和方法给调用组件使用
import { ref, computed } from 'vue'export function useCounter(initialValue = 0) {const count = ref(initialValue)const doubled = computed(() => count.value * 2)function increment() {count.value++}return {count,doubled,increment}
}
import { useCounter } from './useCounter'export default {setup() {const { count, doubled, increment } = useCounter(5)return {count,doubled,increment}}
}Vue递归组件
<script setup>
import { defineProps } from 'vue'const props = defineProps({items: {type: Array,required: true}
})
</script><template><ul><li v-for="item in items" :key="item.id">{{ item.label }}<!-- 如果有子项,则递归调用自身 --><RecursiveMenu v-if="item.children && item.children.length" :items="item.children" /></li></ul>
</template><script>
import { defineComponent } from 'vue'export default defineComponent({name: 'RecursiveMenu'
})
</script>vue异步组件
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent({// 必须是返回 Promise 的函数loader: () => import('./YourComponent.vue'),// 加载超时时间,超过时展示错误组件timeout: 3000,// 组件加载时显示的占位组件loadingComponent: LoadingSpinner,// 加载失败时显示的错误组件errorComponent: ErrorComponent,// 重试次数(默认 0)retries: 1
})
<template><div><h2>异步组件示例</h2><AsyncComp /></div>
</template><script setup>
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from './LoadingSpinner.vue'
import ErrorComponent from './ErrorComponent.vue'const AsyncComp = defineAsyncComponent({loader: () => import('./HeavyComponent.vue'),loadingComponent: LoadingSpinner,errorComponent: ErrorComponent,timeout: 5000
})
</script>在vue2中
export default {components: {AsyncExample: () => import('./AsyncExample.vue')}
}vue的keep-alive
- keep-alive 是一个 Vue全局组件
- keep-alive 本身不会渲染出来,也不会出现在父组件链中
- keep-alive 包裹动态组件时,会缓存不活动的组件,而不是销毁它们
keep-alive 接收三个参数:
- include :可传字符串、正则表达式、数组 ,名称匹配成功的组件会被缓存
- exclude :可传字符串、正则表达式、数组 ,名称匹配成功的组件不会被缓存
- max :可传数字 ,限制缓存组件的最大数量
include 和 exclude ,传数组 情况居多。
keep-alive 主要做了以下部分:
- created :初始化一个 cache、keys ,前者用来存缓存组件的虚拟dom集合,后者用来存缓组件的key集合
- mounted :实时监听 include、exclude 这两个的变化,并执行相应操作
- destroyed :删除掉所有缓存相关的东西
使用场景:
<keep-alive :include="allowList" :exclude="noAllowList" :max="amount"><router-view></router-view>
</keep-alive>
<keep-alive :include="allowList" :exclude="noAllowList" :max="amount"><component :is="currentComponent"></component>
</keep-alive>当组件被<keep-alive>包裹时,会触发两个额外的生命周期钩子:
activated
当被缓存的组件重新激活时调用。适用于需要重新触发数据更新或DOM操作的场景,例如恢复定时器或重新请求数据。
deactivated
当被缓存的组件失活时调用。适用于清理工作,例如清除定时器或取消未完成的请求。
vuex和pinia
1. Vuex
简介
- Vuex 是 Vue 官方推出的状态管理库,面向 Vue 2 设计,也支持 Vue 3。
- 强调集中式存储管理,所有组件的状态集中到 store 中,使用“单一状态树”。
- 基于 Flux 架构,采用 mutations 进行状态同步修改,actions 处理异步逻辑。
核心概念
- State(状态):集中维护的响应式数据。
- Mutations:唯一修改 state 的方法,必须是同步函数。
- Actions:处理异步逻辑,通过
commit触发 mutations。 - Getters:类似计算属性,对 state 数据进行包装和过滤。
- Modules:当应用复杂时,支持模块化拆分 store。
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({state: {count: 0},mutations: {increment(state) {state.count++}},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)}},getters: {doubleCount(state) {return state.count * 2}}
})export default store2. Pinia
简介
- Pinia 是 Vue 官方推荐的新一代状态管理库,设计更简洁友好,完美支持 Vue 3 和 Composition API。
- 语法更接近普通的模块化 JS,更灵活且易学,支持直接调用操作状态。
核心概念
- Store 是一个组合式的状态容器。
- 通过
defineStore定义 store。 - 支持直接修改 state(因为内部用 Proxy 劫持,状态依然响应式)。
- 支持类似计算属性的 getters,以及内置 actions 处理异步和同步逻辑。
import { defineStore } from 'pinia'// 定义 store
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++},incrementAsync() {setTimeout(() => {this.increment()}, 1000)}}
})
import { useCounterStore } from './stores/counter'export default {setup() {const counter = useCounterStore()counter.increment()return { counter }}
}3.对比
| 特性/维度 | Vuex | Pinia |
|---|---|---|
| API 设计 | 需定义 state, mutations, actions | 定义 state、getters、actions,更简洁 |
| 异步处理 | 通过 actions | 直接在 actions 中处理异步 |
| 使用便利性 | 比较繁琐 | 更现代,学习曲线低 |
| TS 支持 | 支持但复杂 | 原生 TypeScript 支持友好 |
| Vue 版本 | Vue 2/3 都支持 | 主要面向 Vue 3,Vue 2 有社区支持 |
| 模块化 | 支持模块拆分 | 自然模块化,多个 store 独立管理 |
| 生态和社区 | 成熟且庞大 | 新兴且快速增长 |
| 功能支持 | 完整、稳定 | 轻量、现代、更灵活 |
vue-router
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'const routes = [{ path: '/', component: Home },{ path: '/about', component: About }
]const router = createRouter({history: createWebHistory(), // 使用 HTML5 History 模式routes
})export default router| 钩子函数 | 触发时机 | 是否可阻止导航 | 是否可访问组件实例 |
|---|---|---|---|
beforeEach | 全局路由开始切换 | 可 | 否 |
beforeResolve | 导航流程最后阶段 | 可 | 否 |
afterEach | 导航完成 | 否 | 否 |
beforeEnter | 路由独享守卫 | 可 | 否 |
beforeRouteEnter | 进入路由组件之前 | 可 | 通过 next 访问 |
beforeRouteUpdate | 路由参数变化,组件复用 | 可 | 是 |
beforeRouteLeave | 离开路由组件时 | 可 | 是 |
