一、组件通信方式
- props/$emit(父子组件通信)
- children (数组)(父子组件通信)
- provide/inject (父子/爷孙组件通信):父组件通过 provide 提供变量,子组件通过 inject 注入变量
- ref / $refs (父子组件通信)
- eventBus 事件总线,on 监听/接收事件,$off 移除事件(全局通信,项目大时难以维护)
- Vuex(全局通信,F5 刷新页面,值会丢失)
- localStorage/sessionStorage
- listeners(父子/爷孙组件通信)
二、Vue 中 data 为什么是函数,不能是对象
因为组件会被实例化多次,data 是对象(引用类型)会导致实例之间的 data 共享。data 用函数并 return,可以使每个实例都拥有一份组件的 data 的副本
三、计算属性 computed 和侦听器 watch 的区别
- 计算属性:复杂逻辑计算时使用,因为计算属性会将它们的响应依赖关系缓存下来,当计算属性依赖的数据变化时,就会触发计算属性更新
- 普通方法:每当触发重新渲染,都会执行
- 侦听器:数据变化时需要执行异步或者开销较大的操作时使用
四、Vue.nextTick
-
nextTick 的作用是,将传入的回调延迟到下次 DOM 更新后执行。
-
nextTick 产生的原因是:Vue DOM 更新是异步执行的,更新数据后,视图并不会马上更新,而是会监听数据变化,并缓存在同一事件循环中(Event Loop:同步代码、异步代码的回调推入任务队列、微任务、宏任务:含 UI Rendering),等同一循环中的数据都变化完后,在统一更新视图。为了确保拿到更新后的 DOM,增加了 nextTick 方法。
-
nextTick 的使用
- 传入回调
- nextTick().then()
-
应用场景
- Vue 的生命周期函数 created() 进行 DOM 操作
- 数据变化后,要使用随数据变化而变化的 DOM
-
实现思路
- 调用 nextTick,会把 nextTick 里的回调函数存进一个任务数组里,再执行这个任务数组之前,nextTick 方法里会先执行一个 Promise.resolve(),再执行回调函数组的方法,这样就确保了 Promise.resolve() 之前已经执行完一个事件循环(包含 DOM 更新)
五、keep-alive
- keep-alive 是 vue 的一个内置组件,作用是使被包含的组件保留状态,或避免重新渲染,也就是组件缓存
六、VUE3 Composition API 组合式API
(一)优点
- 根据逻辑相关性组织代码,提高可读性和可维护性
- 更好地重用逻辑代码,Options API 中 通过 Mixins 重用逻辑代码,容易发生命名冲突且关系不清
- 在 VUE3 中,Composition API 是可选的
(二)setup() 生命周期
- 使用 Composition API 的入口
- 在 beforeCreate 之前调用
- 在 setup 中没有 this
- 返回对象中的所有属性都可以在模板中使用
- setup(props, context) props:组件里的props,context:setup 里的 this
(三)ref
- 返回一个响应式的引用
- ref('name') ,name 是一个响应式对象,在 setup 中使用 name 的值,需要 name.value,在模板中直接用 name
- 用来创建 基本类型 的响应式时,使用的是 Object.defineProperty();
- 用来创建 引用类型 时,调用的是 Reactive 生成 proxy
(四)computed 计算属性
在 setup 中直接给计算属性赋值,是不生效的,需要放在 computed 里的 set 函数里
(五)reactive、toRefs、toRef
-
reactive 创建一个 深度响应式对象
- 一般用来创建引用类型
- 无法直接修改整个 reactive 声明的对象
- 使用 Proxy
- 不能使用 ... 解构,会失去响应
- 可以使用 ...toRef() 将响应式对象转换为引用类型,在页面中直接使用对象中的属性
- toRefs 将响应式对象转换为普通对象
- toRef 针对一个响应式对象的某个属性创建一个 ref ,保持相应及引用关系
- 示例
<template>
<div>
<p>姓名:{{ name }}</p>
<p>年龄:
<button @click="changeAge(-1)">-</button>
<span>{{ age }}</span>
<button @click="changeAge(1)">+</button>
</p>
<p>出生年份:
<button @click="changeYear(-1)">-</button>
{{ year }}
<button @click="changeYear(1)">+</button>
</p>
</div>
</template>
<script>
import { ref, computed, reactive, toRefs, watch } from 'vue';
export default {
props: [
title: String
],
setup () {
// const name = ref('Aubrey')
const data = reactive({
name: 'Aubrey',
age: 18,
year: computed({
get: () => {
return 2022 - data.age
},
set: val => {
data.age = 2022 - val
}
})
});
function changeAge (val) {
data.age += value
console.log(props.title) // props 参数
};
function changeYear (val) {
data.year += val
};
watch(() => props.title, (newTitle, oldTitle) => {
console.log(newTitle, oldTitle)
context.emit('titleChange', newTitle) // context 参数 就是 this
});
return { ...toRefs(data), changeAge, changeYear };
}
}
</script>
七、虚拟 DOM(VDOM)
虚拟 DOM,其实是一个普通的 JavaScript 对象,包含 tag、props、children 三个属性。
虚拟 DOM 如何提升性能:DOM发生变化时,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图。
DOM 节点 通过 h 函数,转换为虚拟 DOM 对象,再 通过 render 渲染函数,渲染到视图。
虚拟 DOM 优势:
- 性能优化(JS 计算代码性能,比 DOM 渲染性能消耗少)
- 跨平台
八、diff 算法
diff 算法,对比新老 vdom 的变化,再通过 patch 方法,将变化更新到视图上。
diff 函数接收两个参数,旧 vdom 对象,新 vdom 对象,返回一个 patches (补丁)。
// h 函数的参数说明
// 第一个: tag 标签名
// 第二个:props 属性
// 第三个:children 子节点(如果是文本节点,则就是 String,
// 否则就是个继续嵌套 tag、props、children 的对象)
const before = h('div', {}, 'before text');
const adter = h('div', {}, 'after text');
const patches = diff(before, after);
1、深度优先遍历算法:子节点顺序变化,diff 算法会认为所有子节点都需要进行 replace,重新将所有子节点的虚拟 DOM 转换成真实的 DOM(全部重新渲染),这种操作十分消耗性能。
优化方式:找到新旧虚拟 DOM 对应的位置,然后进行移动,尽量减少 DOM 操作。通过对子节点添加 key 值,通过 key 值对比,来判断子节点是否移动了。
九、key 的作用
-
key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果
-
在简单模版遍历中,不使用 key,会使新旧虚拟节点跳过 key 值对比,速度更快,节点(标签)会 就地复用,只变更改变的部分内容;
-
key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。
- 更准确
因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
- 更快
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)
4. 使用场景:
(1)想要完整地触发组件的声明周期时,如给一个复用的页面的 绑定:key="router.path"
(2)触发过渡效果
(3)v-for 循环列表
5. 不能用 index 作为 key
(1)改变列表数据顺序时,会增加 DOM 更新
(2)结构中含有表单输入元素时,会导致渲染错误
十、Vue3 生命周期
beforeCreate(setup 没有这个)
Created(setup 没有这个)
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted
activated(复用组件激活)
deactivated(复用组件失活)
errorCaptured
renderTracked
renderTriggered
页面和组件生命周期触发顺序:
十一、Vue Router
-
完整的导航解析流程
- 导航被触发
- 在失活的组件里调用 beforeRouteLeave
- 调用全局的 beforeEach
- 在重用的组件里调用 beforeRouteUpdate
- 调用路由配置的 beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve
- 导航被确认
- 调用全局的 afterEach
- 触发 DOM 更新
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数
-
history 和 hash
-
hash(window.location.hash)
- url 的 # 后的就是 hash,hash变化会触发 onHashChange 事件,但不会触发页面刷新
-
history(window.history)
- 监听window.onpopstate 方法,通过history.pushState()、history.replaceState()控制跳转
- 需要服务器配置将所有路由都重定向到根页面
-
十二、Vue3 对比 Vue2 的变化
-
生命周期
- setup 替换 beforeCreate 和created
- beforeUnmount/unMounted 替换 beforeDestory/destoryed
-
数据劫持的使用的方法
- vue2:Object.defineProperty
- vue3:Proxy
-
组合式 API (Composition API)实现逻辑关注点分离
-
支持多个根节点
-
组件可将部分 DOM 移到 Vue app 之外
-
suspense 异步组件
- #fallback 里显示异步组件加载完成前兜底渲染的内容(如:loading)
- #default 渲染异步组件
-
虚拟 DOM / diff 算法
- 新增 patchFlag 标明节点的类型,如 -1 静态节点,diff 算法比较时忽略其子节点
- patchFlag 简化 diff 算法对比过程
-
事件缓存:cacheHandler,如静态节点上的事件,会随节点一起缓存,不会被重新创建
-
tree-shaking 打包优化:一些之前绑定在 Vue 实例上的 API,改为需要通过 import/export 导入/导出,如 nextTick、set、delete
-
TypeScript 重写
十三、双向绑定原理
VUE 采用数据劫持加订阅-发布模式。主要包含这几个模块:
- Observer 劫持属性,初始化时,给每个属性添加 getter 和 setter,getter 里为每一个属性添加一个订阅器数组,订阅器数组里用来存放这个属性的所有 watcher,setter 里通过 notify 方法通知订阅器里每个 watcher 执行 update 方法
- Watcher 给每个属性添加 update 方法,执行添加的回调函数
- Compiler 模板编译器,初始化解析 DOM 时,为每个使用属性的地方生成一个 watcher,同时添加属性变化后的回调函数
十四、Vue 项目性能优化
-
路由懒加载:() => import()
-
图片懒加载:vue-lazyload
-
按需引入第三方插件:babel-plugin-component
-
无限加载列表虚拟滚动:vue-virtual-scroller
-
服务端渲染 SSR
-
webpack 相关优化
- 预渲染:插件 prerender-spa-plugin
- 提取 chunks 中的公共代码:内置插件 webpack.optimize.splitChunks
- 单独打包 CSS 文件:插件 extract-text-webpack-plugin
- 压缩 CSS:插件 optimize-css-assets-webpack-plugin
- source-map:根据不同环境配置 devtools
- 生产环境的包分析工具:webpack-bundle-analyzer
- 代码懒加载:() => import ()
- prefetch:预获取(浏览器空余时)
import ( /* webpackPrefetch: true */ './imgDec').then()
- preload:预加载
import ( /* webpackPreload: true */ './imgDec').then() 10. 利用浏览器缓存(强缓存、协商缓存)