Vue
对Vue的理解
Vue 是一个构建数据驱动的渐进性框架,目标是通过API实现 响应数据绑定 和 视图更新
Vue的两大核心
- 数据驱动
组件系统
Vue生命周期
Vue实例从创建到销毁的过程
graph LR 开始创建 --> 初始化数据-->编译模板-->挂载DOM-->渲染-->更新-->渲染;更新-->卸载
生命周期的作用
生命周期中有多个事件钩子,能让开发者在控制整个vue实例的过程时更容易形成良好的逻辑判断
created
和mounted
created | mounted | |
---|---|---|
调用时机 | 模板渲染成 HTML 前 | 模板渲染成 HTML 后 |
常见作用 | 初始化属性值,再渲染成视图 | 操作DOM节点 |
定时器
在 beforeDestroy()
清除
clearInterval(this.timer);
this.timer = null;
v-model
的使用
用于表单数据的双向绑定,其实是语法糖,背后做了两个操作:
- v-bind绑定value属性
v-on绑定input事件
Vue响应式(双向数据)原理
通过 数据劫持 结合 发布订阅模式 的方式来实现
- Vue2:通过Object.defineProperty() 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue3:仅仅是将数据劫持的方式由Object.defineProperty更改为ES6的Proxy代理
Vue3相对Vue2的优化
- 双向数据绑定方面:
Vue2的object.defineProperty一次只能劫持对象的单个属性,从而需要通过递归+遍历对每个对象的每个属性进行数据监控;如果属性值是对象的话,还需要深度遍历
而Vue3.0中的proxy可以一次性地劫持对象的所有属性的setter、getter,还可以代理数组,也可以代理动态添加的属性,有13种劫持操作 - 性能方面快1.2~2倍:
(1) diff方法优化
vue2中的虚拟dom是全量的对比(每个节点不论写静态还是动态的都会比较)
vue3新增了静态标记(patchflag)与上次虚拟节点对比时,只对比带有patch flag的节点(动态数据所在的节点);可通过flag信息得知当前节点要对比的具体内容
(2) 静态提升
vue2无论元素是否参与更新,每次都会重新创建然后再渲染
vue3对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
(3) 时间侦听器缓存
默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化
但是因为是同一个函数,所以不用追踪变化,直接缓存起来复用即可
(4) ssr渲染
- 按需编译,体积比vue2.x更小
- 组合API(类似react hooks)
- 更好的Ts支持
- 暴露了自定义渲染API
- 更先进的组件
Fragment:模板可以有多个根元素
虚拟DOM
虚拟DOM相对于浏览器所渲染出来的真实 DOM,在 React,Vue 等技术出现之前, 改变页面展示内容只能遍历查询 DOM 树找到需要修改的 DOM,然后修改样式行为或者结构,来更新UI,但是这种方式相当消耗计算资源,每次查询DOM几乎都需要遍历整棵DOM树,因此建立一个与真实DOM树对应的虚拟DOM对象(JavaScript对象),以对象嵌套的方式来表示DOM树,那么每次DOM的更改就变成了对象属性的更改,这样一来就能通过diff算法查找变化要比查询真实的DOM树的性能开销小
渲染过程
graph TD 将template标签里面的内容编译成render函数 --> 挂载实例:根据render函数的根节点递归生成虚拟DOM树-->通过diff算法对比虚拟DOM,渲染到真实DOM-->新的DOM操作使得DOM树发生改变-->通过diff算法对比虚拟DOM,渲染到真实DOM
key的作用
为了高效的更新虚拟DOM
给虚拟DOM的每个节点 VNode 添加唯一 id,让其可以依靠 key,更快速准确地拿到 oldVnode 中对应的 VNode
【设置key不推荐使用index或者随机数,因为增删子项的时候损耗性能较大】
v-if
和v-show
- v-if在DOM层面决定元素是否存在,会引起重排重绘
v-show在CSS层面决定是否将元素渲染出来(实际上该元素一直存在)
v-if
和v-for
优先级
v-if |
v-for |
|
---|---|---|
Vue 2 | 优先 | |
Vue 3 | 优先 |
解决方案 :
- 父元素使用v-if,子元素使用v-for
- 使用计算属性
computed
computed() {
list() {
return [1, 2, 3].filter(item => item !== 2);
}
}
data
是个函数并且返回一个对象 / data
为什么要用return
因为一个组件可能会多处调用,每次调用会执行data函数并返回新的数据对象,因此这样可以避免多处调用之间的数据污染
Vue 如何监听键盘事件
- @keyup.方法
addEventListener
组件通信
- 父子的传参及方法调用:props / $emit、$parent / $children
- 祖孙的传参:provide / inject API、$attrs / $listeners
- 兄弟的传参:bus.js、Vuex
- 路由的传参:query、params
父子 | 祖孙/隔代 | 兄弟 | |
---|---|---|---|
props / $emit | √ | ||
ref与 $parent / $children | √ | ||
$attrs / $listeners | √ | ||
provide / inject | √ | ||
EventBus($emit / $on) | √ | √ | √ |
Vuex | √ | √ | √ |
localStorage/Cookie等都可以
删除数组用 delete 和 Vue.delete 的区别
delete:只是被删除数组成员变为 empty / undefined,其他元素键值不变
- Vue.delete:直接删了数组成员,并改变了数组的键值
(对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制)
- Vue.delete:直接删了数组成员,并改变了数组的键值
计算属性computed
和属性检测watch
cpmputed:当且仅当计算属性依赖的 data 改变时才会自动计算
computed |
watch |
|
---|---|---|
首次运行 | √ | × |
默认依赖 | 深度(推荐使用) | 浅度 |
调用时 | 在模板渲染 | 只需修改元数据 |
合适性 | 筛选、不可异步 | 开销较大、异步操作 |
特征 | 根据页面变化而变化(用于计算) | 监听页面状态而变化(用于监听) |
Vue组件封装过程
graph LR
Vue.extend 创建组件-->Vue.component 注册组件
v-for
后使用this.$refs
报错domundefined
组件初始化到第一次渲染完成的mounted周期里,只是渲染了组件模板的静态数据,并没有初始化动态绑定的dom,所以在mounted周期里面操作获取不到dom
解决方法:
- 把this.$nextTick放在获取到v-for绑定的数据并赋值之后,也就是触发响应式更新之后再进行操作
把操作dom的操作放到updated生命周期里,但是这样每次更新视图都会触发该操作
scoped
的实现原理- 通过PostCSS给所有dom都添加了唯一的动态属性
通过PostCSS也给css选择器额外添加对应的属性选择器,来选择组件中的dom
插槽
- 默认插槽
- 具名插槽
作用域插槽
Vue CLI
src目录每个文件夹和文件的用法
- assets文件夹是放静态资源;
- components是放组件;
- router是定义路由相关的配置;
- app.vue是一个应用主组件;
main.js是入口文件
static和assets的区别
原理:webpack 如何处理静态资源
static assets 内容 类库 项目资源 资源 直接引用 被 webpack 打包
(static放别人家的资源,assets放自己家的资源)
引入第三方库
- 绝对路径直接引入
- 在 webpack 中配置
alias
[ˈeɪliəs] 在 webpack 中配置
plugins
[ˈplu:genz]Vue-Router
路由实现原理
- 利用URL中的hash("#")
利用History interface在HTML5中新增的方法
hash模式和history模式
hash模式
history模式
浏览器支持版本 IE、低版本 HTML5新推出的API 刷新 重新加载 404 URL中是否带 # √ × # 后面的hash变化 不会重新加载,会触发hashchange事件渲染对应内容 重新加载,会触发popState事件渲染对应内容
传参
- name 传参
- URL 传参
的to传参用 path 匹配路由,通过 query 传参
二级路由 / 嵌套路由哦
使用路由导航
和路由容器
,配置children路由守卫触发流程
不同组件(A组件跳转到B组件)
graph TD 触发导航-->调用A组件的路由守卫beforeRouteLeave-->调用全局路由前置守卫beforeEach-->调用B路由独享守卫beforeEnter-->解析异步路由组件B-->调用B的组件内路由守卫beforeRouteEnter-->调用全局路由解析守卫beforeResolve-->确认导航-->调用全局路由钩子afterEach-->渲染B组件DOM
keep-alive
keep-alive用于保存组件的渲染状态
不希望组件被重新渲染影响用户体验和降低性能,而是希望组件可以缓存下来,维持当前的状态,这时候就可以用到keep-alive
组件
(通常搭配生命周期activated
使用)
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说就是:应用遇到多个组件共享状态时,使用vuex。
vuex的流程
graph TD
页面通过 mapAction 异步提交事件到 action-->action 通过 commit 把对应参数同步提交到 mutation-->mutation 修改 state 中对应的值-->通过 getter 传递对应值-->在页面的 computed 中通过 mapGetter 来动态获取 state 中的值
vuex属性
mapAction:State , Getter , Mutation , Action , Module
- state:vuex的基本数据(state),用来存储变量
- getter:从基本数据(state)派生的数据,相当于state的计算属性
- mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。
- action:和mutation的功能大致相同
不同之处: - Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构清晰,方便管理。
ajax 写在 methods 中还是 actions 中
- 仅在请求组件内使用:写在组件的 methods 中
- 在其他组件复用:写在 vuex 的 actions 中
(包装成 promise 返回,在调用处用 async await 处理返回的数据)