Vue知识点整理
Vue是一套用于构建用户界面的渐进式框架,Vue的核心库只关注视图层
使用声明式编程,开发者只需关注业务逻辑和视图,繁琐的处理由Vue处理。
1.组件通信常用的方式有以下8种:
- props (子不能直接改父)
- $emit/
$on(自定义事件) $children/$parent(拿到当前组件的父组件,子可以直接改父)- $attrs/
$listeners - ref
- $root (拿到根组件)
- eventbus(创建一个EventBus.js文件并向外共享一个Vue的实例对象;发送方调用bus.$emit('事件名称',数据)触发自定义事件;接收方使用bus.$on('事件名称',事件处理函数)注册一个自定义事件;)
-
vuex/pinia
父子组件:props/$emit/$parent/ref/$attrs
兄弟组件:$parent/$root/eventbus/vuex
跨层级:eventbus/vuex/provide+inject(依赖注入)
子组件不要修改父组件的值(单向数据流)2.v-if和v-for优先级
首先不应该将这两个放在一起使用
vue2中v-for优先级高于v-if,在vue3中相反。
3.Vue生命周期
Vue生命周期总共可分为8个阶段:创建前后、挂载前后、更新前后、销毁前后,以及一些特殊场景的生命周期,Vue3中新增了三个用于调试和服务端渲染场景。
销毁前后3版本有变化:beforeDestroy -> beforeUnmount destroyed -> unmounted
keep-live缓存的组件激活时:activated、停用时 deactivated
捕获一个来自子孙组件的错误:errorCaptured
vue3新增:renderTracked 调试钩子,响应式依赖被收集时调用;renderTriggered 调试钩子,响应式依赖被触发时调用;serverPrefetch ssr only,组件实例在服务器上被渲染前调用。
在使用CompositionAPI时,setup在beforeCreate前。使用OptionsAPI时,它在beforeCreate和created中间初始化。
组合式API中不存在onBeforeCreate和onCreated
vue父子组件创建、挂载顺序
创建时父组件先创建,挂载时子组件先挂载。
parent created -> parent beforeMount -> child created -> child mounted -> parent mounted
发送网络请求为什么不在beforeCreate中?
此时还没有创建实例,拿不到data、methods等等
怎么在created中拿DOM?
答:通过异步的方式,比如setTimeout、$nextTick
4.双向绑定的使用和原理
vue中的双向绑定是一个指令:v-model,可以绑定一个响应式数据到视图,同时视图中变化能改变该值。
v-model是语法糖,默认情况下等于:value
和@input
。通常在表单使用,也可用在自定义组件上。
vue3中一个组件支持多个v-model,可自定义名称v-model:abc
,类似vue2的.sync
修饰符,3中已移除。
5.Vue中的权限管理
通常分为页面权限和按钮权限
小项目一般使用前端方案:把所有路由信息在前端配置,用户登录后根据角色过滤出路由表。通过router.addRoute()
动态添加路由。
大点的项目一般使用后端方案:把所有路由信息存储在数据库中,用户登录后将路由信息返回给前端,前端再通过addRoute
动态添加路由。
按钮权限的控制通常会实现一个指令,例如v-permission
,在指令的mounted
钩子中可以判断当前用户角色的按钮权限是否和指令绑定的权限有交集。
6.什么是响应式
数据响应式就是能够检测数据的变化并对变化做出响应的机制。
MVVM解决的核心问题就是连接数据层和视图层,通过数据驱动应用,数据变化视图更新,要做到这一点就需要对数据做响应式处理,这样数据变化就可以更新视图
以vue为例,通过数据响应式加上虚拟DOM和patch算法,开发者仅需操作数据关心业务,无需DOM操作,大大提升开发效率。
Vue2中的数据响应式会根据数据类型来做不同的处理:如果是对象则采用Object.deineProperty()
方法定义数据拦截,多层对象通过递归实现劫持,如果是数组则通过覆盖数组对象原型的7个方法(pop/push/unshift/shift/splice/sort/reverse)【如果给每个元素都添加getter/setter太消耗性能,数组中如果是对象也会进行递归劫持】。存在的缺点:初始化时会一次性递归造成性能损失,新增或删除属性时需要用户使用Vue.set/delete
这样的API才能生效,不支持ES6中的Map、Set数据结构。
为了解决这些问题,Vue3中重写了这一部分。利用ES6的Proxy代理要添加响应式的数据,改善了初始化性能和内存消耗,另外将响应式的实现代码抽取为独立的reactivity包,使得第三方的扩展开发可以更灵活的使用它。
Proxy是对整个对象/数组代理,返回一个新的代理对象,就可以拦截属性新增、删除。
Vue3 的响应式核心是 Proxy + Reflect,通过 「访问时递归」 的机制实现按需响应式化。
7.Vuex
state 定义全局属性 辅助函数 mapState
this.$store.state.xxx
是可以直接修改vuex中的数据的,使用辅助函数不可修改。(规定需要通过mutation进行修改,也便于devtools跟踪)
getters 针对state数据进行二次计算
mutations 存放同步方法,参数state,修改state数据 辅助函数mapMutations
actions 存放异步方法 参数对象{commit,state} , 修改state 辅助函数mapActions
modules 把vuex再次进行模块之间的划分
8.VueRouter
hash 模式 URL带 #
history 模式 URL不带 #
会寻找页面,需要后端配置
$route 是当前路由对象
路由守卫分三类:全局守卫、路由独享守卫、组件内守卫
全局:beforeEach、afterEach(路由进入之后)
路由独享守卫:beforeEnter
组件内守卫:beforeRouteEnter路由进入之前、beforeRouteUpdate路由更新前、beforeRouteLeave路由离开前
自己实现一个vue-router的思路:
首先考虑的是vue路由要解决的问题:一个SPA应用,用户点击跳转内容切换,页面不刷新,路由以插件形式存在
借助hash或者history api实现URL变化页面不刷新
同时监听hashchange事件或者popstate事件处理跳转(popstate事件通常是浏览器的前进、后退)
根据hash值或者state值从路由表中匹配对应组件并渲染
1.定义一个createRouter
函数,返回路由器实例,内部做几件事情:
- 保存用户传入的配置项
- 监听hash或者popstate事件
- 回调里根据path匹配对应路由
2.将router定义成一个Vue插件,即实现install方法,内部做两件事 - 实现两个全局组件:
router-link
和router-view
,分别实现页面跳转和内容显示 - 定义两个全局变量:$router和$route,组件内可以访问当前路由和路由器实例
9.$set
例子:数据更新了而视图没更新,Vue2中通过下标修改数组
this.arr[1] = 'abc' // 视图不会更新,Vue2中数组的响应式是靠重写数组方法实现的
this.$set(this.arr, '1', 'abc') // 利用Vue提供的API可完成响应式数据的修改
// 也用于对象属性的新增、删除操作(defineProperty监听不到)
10.$nextTick
定义:等待下一次DOM更新的方法
Vue有个异步更新策略,意思是如果数据变化,Vue不会立即更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。
场景:
在created中也能获取DOM
响应式数据变化后获取DOM更新后的状态,比如希望获取列表更新后的高度
created() {
this.$nextTick(() => {
console.log( this.$el )
})
}
11.key的作用
结论:主要是优化diff算法的性能
key是diff算法中判断两个节点是否是同一个节点的必要条件(key和元素类型等),相同节点可复用,减少DOM操作
虚拟dom——virtual dom,提供一种简单js对象去代替复杂的 dom 对象,从而优化 dom 操作。virtual dom 是“解决过多的操作 dom 影响性能”的一种解决方案。virtual dom 很多时候都不是最优的操作,但它具有普适性,在效率、可维护性之间达到平衡。虚拟DOM方便实现跨平台,直接操作dom是有限制的比如diff、clone
等操作,在js中会方便很多。
diff 算法是一种优化手段,将前后两个模块进行差异化比较,修补(更新)差异的过程叫做 patch,也叫打补丁。只有当新旧子节点的类型都是多个子节点时,核心 Diff 算法才派得上用场。diff的目的是时间换空间:尽可能通过移动旧节点,复用旧节点DOM元素,减少新增DOM操作。通过首首、尾尾、首尾、尾首以及在旧节点列表遍历等方式逐个试探去找可复用的旧节点。
vue3引入了最长递增子序列优化diff:去掉相同的前缀和后缀,也就是首首、尾尾都比较完后剩余的旧节点列表和新节点列表进行diff。在新节点列表中用一个数组,统计新节点出现在旧节点相同元素的index;对这个数组求最长递增子序列。递增子序列的节点不需要移动(即使不连续),因为它们在新旧节点序列中的相对位置是一样的。
12.computed和watch
computed 具有响应式返回值(只读,传递对象时可写)
计算属性具备缓存,依赖的值不发生变化,对其取值时计算属性方法不会重新执行
计算属性可以简化模版中复杂的表达式,计算属性中不支持异步逻辑
watch 监听变化,执行回调,适合当数据改变时执行异步或者开销较大的操作
vue2中监听对象的属性可以直接使用obj.xxx
,在vue3中需要通过方法返回值的形式()=>obj.xxx
13.一些Vue的最佳实践
1️⃣ 编码风格方面:
- v-for务必加key,切不和v-if写在同一个元素上
2️⃣ 性能方面 - 路由懒加载减少打包后app.js的体积
- 利用SSR做SEO优化,减少首屏加载时间
- 利用v-once渲染那些不需要更新的内容
- 长列表用虚拟滚动(开源库 vue-virtual-scroller,只渲染视口范围内的)
- 对深层嵌套对象的大数组使用shallowRef或者shallowReactive降低开销
- v-show/v-if
- keep-live
- v-memo
- 图片懒加载 使用 vue-lazyload
- 按需加载第三方组件库
3️⃣ 安全 - 小心使用v-html
14.ref和reactive
ref 通常用于处理单值的响应式(基本数据类型),底层 使用Object.defineProperty
reactive 用于处理对象类型的数据响应式,底层使用 Proxy代理对象,解构会失去响应式
15.Vue组件化的理解
组件化的好处:可复用、高内聚、低耦合、可组合,降低更新范围,只重新渲染变化的组件
组件的核心组成:模板、属性、事件、插槽、生命周期
Vue中每个组件都有一个渲染函数,响应式数据变化后调用函数完成页面更新【Vue2中:watcher,Vue3中:effect】
组件要合理划分,如果不拆分组件,那更新的时候整个页面都要更新,如果拆分的过多也会导致性能浪费。
如何组件化开发:
在Vue中进行组件化开发,首先要明确组件的划分原则,一般会按照功能或者业务逻辑划分,像把导航栏、侧边栏这些常用的部分做成独立组件,然后在Vue里通过import引入,在components选项里注册它,使用的时候像标签一样调用就可以了,而且组件之间可以通过props传递数据,用emit触发事件通信。
16.Watch和WatchEffect
watch监听一个或多个响应式数据源,并在数据源变化时调用回调函数
watchEffect立即运行一个函数,然后被动的追踪它的依赖,当这些依赖改变时重新执行该函数
17.Vue组件的data为什么是函数?
目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。
所以需要通过工厂函数返回全新的data作为组件的数据源。
18.过滤器
常用于文本格式化、单位转换,使用|
符号在差值表达式中使用,通过Vue.filter('name',()=>{})
定义,Vue3中已移除,使用方法代替。
19.v-once指令
只渲染元素和组件一次,这可用于性能优化。vue3.2之后增加了v-memo
通过依赖列表的方式控制页面渲染
20.mixin混入
可以用来扩展组件,将公共逻辑进行抽离。在需要该逻辑时进行混入,如果混入的数据和组件本身的数据有冲突,会采用组件的数据为准。
mixin的缺陷:数据命名冲突问题、数据来源问题,在Vue3中使用组合式API提取公共逻辑就比较方便
props、methods、inject、computed 同名时被替换
data被合并
生命周期和watch方法会被合并成队列
components、directives、filters会在原型链上叠加
21.组件的name选项
增加name选项会在components属性中增加组件本身,实现组件的递归调用
可以标识组件的具体名称方便调试和查找对应组件
22.自定义指令
指令的生命周期:
- bind 只调用一次,指令第一次绑定到元素时调用,在这里可进行一次性初始化配置
- inserted 被绑定元素插入父节点时调用
- update 所在组件的VNode更新时调用,但可能发生在其子VNode更新前
- componentUpdated 指令所在组件的VNode及其子VNode全部更新后调用
- unbind 只调用一次,指令与元素解绑时调用
v-lazy
图片懒加载、v-debounce
防抖、v-has
按钮权限、 拖拽指令等
23.Vue中常见的设计模式
- 单例模式 就是整个程序有且只有一个实例,Vuex中的store
- 工厂模式 传入参数即可创建实例(createElement)
- 发布订阅模式 订阅者把自己想订阅的事件注册到调度中心,当该事件触发的时候,发布者发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码
- 观察者模式 watcher和dep的关系
- 代理模式 给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用
- 装饰模式 vue2装饰器的用法(对功能进行增强@)
- 中介者模式 中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信vuex
- 策略模式 指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案 mergeOptions
- 外观模式 提供了统一的接口,用来访问子系统中的一群接口
24.Vue中的性能优化有哪些?
- 数据层级不宜过深,合理设置响应式数据(Vue2一次性递归)
- 通过Object.freeze()方法冻结属性
- 使用数据时缓存值的结果,不频繁取值
- 合理设置Key属性
- v-show和v-if的选取
- 控制组件颗粒度(Vue采用组件级更新)
- 采用函数式组件(函数式组件开销低)
- 采用异步组件(借助webpack分包的能力)
- 使用keep-alive缓存组件v-once
- 分页、虚拟滚动、时间分片等策略
25.单页面应用首屏加载速度慢怎么解决?
- 使用路由懒加载、异步组件,实现组件呢拆分,减少入口文件体积大小(优化体验骨架屏)
- 抽离公共代码,采用splitChunks进行代码分割
- 组件加载采用按需加载的方式
- 静态资源缓存,采用HTTP缓存(强制缓存、对比缓存)、使用localStorage实现缓存资源
- 图片资源的压缩,雪碧图、对小图片进行base64减少http请求
- 打包时开启gzip压缩处理compression-webpack-plugin插件
- 静态资源采用CDN提速(终极手段)
- 使用SSR对首屏做服务端渲染
26.Vue项目中怎么解决跨域
- CORS 由服务端配置,允许指定的客户端访问服务器
- 构建工具中设置反向代理、使用nginx做反向代理
- 使用Websocket进行通信
- 搭建BFF(Backend For Frontend)层解决跨域问题
27.Vue3新特性
组合式API
Teleport 传送门组件
Fragments 允许组件返回多个根元素
Vue 3 的虚拟 DOM 实现经过优化,能够更高效地进行 DOM diff 和更新,减少了不必要的重渲染
TS支持
新的生命周期钩子
Suspense,开发者可以指定加载状态的展示内容,从而提供更流畅的用户体验
Vue 3 引入了更小的核心库,并支持 Tree-shaking 技术,可以在构建时去掉未使用的代码,减少了最终构建的文件大小
28.测试
Jest工具做单元测试 @vue/vue3-jest
,@vue/test-utils
Vitest完成组件的单元测试
E2E测试其实是对单元测试的一种补充,通常使用Cypress。