Vue
对Vue的理解
Vue 是一个构建数据驱动的渐进性框架,目标是通过API实现 响应数据绑定 和 视图更新
Vue的两大核心
- 数据驱动
- 组件系统
Vue生命周期
Vue实例从创建到销毁的过程
graph LR
开始创建 --> 初始化数据-->编译模板-->挂载DOM-->渲染-->更新-->渲染;更新-->卸载
生命周期的作用
生命周期中有多个事件钩子,能让开发者在控制整个vue实例的过程时更容易形成良好的逻辑判断
created和mounted
created有$data,mounted有$el
| created | mounted | |
|---|---|---|
| 调用时机 | 模板渲染成 HTML 前 | 模板渲染成 HTML 后 |
| 常见作用 | 初始化属性值,再渲染成视图 | 操作DOM节点 |
定时器
在 beforeDestroy()清除
clearInterval(this.timer);
this.timer = null;
v-model
原理
用于表单数据的双向绑定,其实是语法糖,背后做了两个操作:
- v-bind绑定value属性
- v-on绑定input事件
实现
<input type="text" :value="msg" @input="msg=$event.target.value" />
// 先给输入框的value绑定变量,这叫做属性绑定
// 再给输入框绑定input事件,一旦输入,就将输入框的值赋给msg变量,从而实现双向绑定
自定义组件的v-model
组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新
注意:仍然需要在组件的 props 选项里声明 checked 这个 prop
Vue响应式(双向数据)原理
通过 数据劫持 结合 发布订阅模式 的方式来实现
- Vue2:通过
Object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调 - Vue3:将数据劫持的方式由
Object.defineProperty更改为ES6的Proxy代理
为什么 Vue3 抛弃Object.defineProperty 采用Proxy
Object.defineProperty本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组
push();
pop();
shift();
unshift();
splice();
sort();
reverse();
由于只针对了以上 7 种方法进行了 hack 处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。
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树的性能开销小
<template>渲染过程
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
单向数据流
vue组件通信里面父子通信是自上而下的,不能直接修改props里面属性
组件通信
- 父子的传参及方法调用: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 不能检测到属性被删除的限制)
计算属性computed和属性检测watch
cpmputed:当且仅当计算属性依赖的 data 改变时才会自动计算
| computed | watch | |
|---|---|---|
| 首次运行 | √ | × |
| 默认依赖 | 深度(推荐使用) | 浅度 |
| 调用时 | 在模板渲染 | 只需修改元数据 |
| 合适性 | 筛选、不可异步 | 开销较大、异步操作 |
| 特征 | 根据页面变化而变化(用于计算) | 监听页面状态而变化(用于监听) |
v-for后使用this.$refs报错domundefined
ref:用来获取dom
组件初始化到第一次渲染完成的mounted周期里,只是渲染了组件模板的静态数据,并没有初始化动态绑定的dom,所以在mounted周期里面操作获取不到dom
解决方法:
- 把
this.$nextTick放在获取到v-for绑定的数据并赋值之后,也就是触发响应式更新之后再进行操作 - 把操作dom的操作放到
updated生命周期里,但是这样每次更新视图都会触发该操作
插槽
- 默认插槽
- 具名插槽
- 作用域插槽
scoped的实现原理
- 通过PostCSS给所有dom都添加了唯一的动态属性
- 通过PostCSS也给css选择器额外添加对应的属性选择器,来选择组件中的dom
样式穿透
scss:父元素/deep/子元素stulus:父元素>>>子元素
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 传参
<router-link>的to传参- 用 path 匹配路由,通过 query 传参
二级路由 / 嵌套路由哦
使用路由导航<router-link>和路由容器<router-view>,配置children
路由守卫触发流程
- 不同组件(A组件跳转到B组件)
graph TD
触发导航-->调用A组件的路由守卫beforeRouteLeave-->调用全局路由前置守卫beforeEach-->调用B路由独享守卫beforeEnter-->解析异步路由组件B-->调用B的组件内路由守卫beforeRouteEnter-->调用全局路由解析守卫beforeResolve-->确认导航-->调用全局路由钩子afterEach-->渲染B组件DOM
keep-alive
keep-alive用于保存组件的渲染状态
不希望组件被重新渲染影响用户体验和降低性能,而是希望组件可以缓存下来,维持当前的状态,这时候就可以用到keep-alive组件
(通常搭配生命周期activated和deactived使用)
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 处理返回的数据)