Vue 全家桶的各种重点技术点
什么是库?什么是框架?
- 库 是将代码集合成一个产品,库是我们调用库中的方法实现自己的功能。
- 框架 是为解决一类问题而开发的产品,框架是我们在指定的位置编写好代码,框架帮我们调用。
声明式框架
Vue3 依旧是声明式的框架,用起来简单(代码更加简单,不需要关注实现,按照要求填代码就可以)
- 命令式和声明式的区别?
- jQuery 时代编写的代码都是命令式的,命令式框架重要的特点是
关注过程
- 声明式框架更加
关注结果
。命令式的代码封装带了 Vue 源码中,过程靠 Vue 内部来实现
- jQuery 时代编写的代码都是命令式的,命令式框架重要的特点是
// 命令式编程:
let numbers = [1, 2, 3, 4, 5]
let total = 0
for (let i = 0; i < numbers.length; i++) {
total += numbers[i] // 关注了过程
}
console.log(total)
// 声明式编程:
let total1 = numbers.reduce((memo, cur) => {
return memo + cur
})
console.log(total1)
Monorepo 管理项目
Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)。Vue3 源码采用了 monorepo 方式进行管理,将模块拆分到 packages 目录中
- 一个仓库可维护多个模块,不用到处找仓库
- 方便版本管理和依赖管理,模块之间的引用,调用都非常方便
Vue 的基本使用
$ npm init -y
$ npm install vue
- 安装
npm install -g @vue/cli
npm install -g @vue/cli-service-global
vue create vue-online-edit
- 初始化
? Check the features needed for your project:
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
>(*) Router
>(*) Vuex
>(*) CSS Pre-processors
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
vue serve
是用来单独开发一个 vue 组件
用的。vue serve
这个命令需要全局安装之后,才能使用。使用 npm install -g @vue/cli-service-global
安装之后,再来使用 vue serve
命令。
Vue 中的模板
<script src="node_modules/vue/dist/vue.js"></script>
<!-- 3.外部模板 -->
<div id="app">{{name}}</div>
<script>
const vm = new Vue({
el: '#app',
data: {
name: 'tmc',
age: 25,
},
// 2.内部模板
template: '<div>{{age}}</div>',
// 1.render函数
render(h) {
return h('h1', ['hello,', this.name, this.age])
},
})
</script>
注意:我们默认使用的是 runtime-with-compiler
版本的 vue
,带 compiler
的版本才能使用 template
属性,内部会将 template
编译成 render
函数。
流程如下:
- 会先查找用户传入的
render
- 若没有传入
render
则查找template
属性 - 若没有传入
template
则查找el
属性,如果有el
,则采用el
的模板
响应式原则
Vue
内部会递归的去循环vue
中的data
属性,会给每个属性都增加getter
和setter
,当属性值变化时会更新视图- 重写了数组中的方法,当调用数组方法时会触发更新,也会对数组中的数据(对象类型)进行了监控
Vue 中的缺陷:
- 对象默认只监控自带的属性,新增的属性响应式不生效 (层级过深,性能差)
- 数组通过索引进行修改 或者 修改数组的长度,响应式不生效
Vue 额外提供的 API:
vm.$set(vm.arr, 0, 100) // 修改数组内部使用的是splice方法
vm.$set(vm.address, 'number', '6-301') // 新增属性通过内部会将属性定义成响应式数据
vm.$delete(vm.arr, 0) // 删除索引,属性
为了解决以上问题,Vue3.0 使用 Proxy 来解决
let obj = {
name: { name: 'jw' },
arr: ['吃', '喝', '玩'],
}
let handler = {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, value) {
let oldValue = target[key]
if (!oldValue) {
console.log('新增属性')
} else if (oldValue !== value) {
console.log('修改属性')
}
return Reflect.set(target, key, value)
},
}
let proxy = new Proxy(obj, handler)
实例方法
- vm._uid -> 每个实例的唯一标识
- vm.$data === vm._data -> 实例的数据源
- vm.$options -> 用户传入的属性
- vm.$el -> 当前组件的真实 dom
- vm.$nextTick -> 等待同步代码执行完毕
- vm.$mount -> 手动挂载实例
- vm.$watch -> 监控数据变化
自定义指令 - 钩子函数
指令定义对象可以提供如下几个钩子函数:
- bind:只调用一次,指令第一次绑定到元素时调用
- inserted:被绑定元素插入父节点时调用
- update:所在组件的 VNode 更新时调用,组件更新前状态
- componentUpdated:所在组件的 VNode 更新时调用,组件更新后的状态
- unbind:只调用一次,指令与元素解绑时调用
// 1.el 指令所绑定的元素,可以用来直接操作 DOM
// 2.bindings 绑定的属性
// 3.Vue编译生成的虚拟节点 (context)当前指令所在的上下文
bind(el,bindings,vnode,oldVnode){ // 无法拿到父元素 父元素为null
console.log(el.parentNode,oldVnode)
},
inserted(el){ // 父元素已经存在
console.log(el.parentNode)
},
update(el){ // 组件更新前
console.log(el.innerHTML)
},
componentUpdated(el){ // 组件更新后
console.log(el.innerHTML)
},
unbind(el){ // 可用于解除事件绑定
console.log(el)
}
Vue 组件通信
props
和$emit
父组件向子组件传递数据是通过prop
传递的,子组件传递数据给父组件是通过$emit
触发事件来做到的$attrs
和$listeners
A->B->C
。Vue 2.4
开始提供了$attrs
和$listeners
来解决这个问题$parent
,$children
$refs
获取实例- 父组件中通过
provider
来提供变量,然后在子组件中通过inject
来注入变量 envetBus
平级组件数据传递 这种情况下可以使用中央事件总线的方式vuex
状态管理
掌握 Vue 重点
一、插槽
二、Transition 组件
三、Keep-Alive 组件
一、Keep-alive 是什么
keep-alive 是 vue 中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染 DOM
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
keep-alive 可以设置以下 props 属性:
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
- max - 数字。最多可以缓存多少组件实例
keep-alive 的基本用法:
<keep-alive>
<component :is="view"></component>
</keep-alive>
使用 includes 和 exclude:
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated 与 deactivated):
- 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
- 再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated
二、使用场景
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用 keep-alive
举个栗子: 当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要 keep-alive
在路由中设置 keepAlive 属性判断是否需要缓存
{
path: 'list',
name: 'itemList',
component: itemList,
meta: {
keepAlive: true,
title: '列表页'
}
}
使用 <keep-alive>
<div id="app">
<keep-alive>
<!-- 需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
三、原理分析
keep-alive 是 vue 中内置的一个组件; 源码位置:src/core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number],
},
created() {
this.cache = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
this.$watch('include', (val) => {
pruneCache(this, (name) => matches(val, name))
})
this.$watch('exclude', (val) => {
pruneCache(this, (name) => !matches(val, name))
})
},
render() {
/* 获取默认插槽中的第一个组件节点 */
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
/* 获取该组件节点的componentOptions */
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
const name = getComponentName(componentOptions)
const { include, exclude } = this
/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
if (
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
/* 获取组件的key值 */
const key =
vnode.key == null
? componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
/* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
/* 如果没有命中缓存,则将其设置进缓存 */
cache[key] = vnode
keys.push(key)
// prune oldest entry
/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
},
}
function pruneCacheEntry(
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
/* 判断当前没有处于被渲染状态的组件,将其销毁*/
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
keep-alive 的最强大缓存功能是在 render 函数中实现;
首先获取组件的 key 值:
const key =
vnode.key == null
? componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
拿到 key 值后去 this.cache 对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,如下:
/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
/* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */
remove(keys, key)
keys.push(key)
}
直接从缓存中拿 vnode 的组件实例,此时重新调整该组件 key 的顺序,将其从原来的地方删掉并重新放在 this.keys 中最后一个
this.cache 对象中没有该 key 值的情况,如下:
/* 如果没有命中缓存,则将其设置进缓存 */
else {
cache[key] = vnode
keys.push(key)
/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
表明该组件还没有被缓存过,则以该组件的 key 为键,组件 vnode 为值,将其存入 this.cache 中,并且把 key 存入 this.keys 中
此时再判断 this.keys 中缓存组件的数量是否超过了设置的最大缓存数量值 this.max,如果超过了,则把第一个缓存组件删掉
四、思考题:缓存后如何获取数据
解决方案可以有以下两种:
- beforeRouteEnter
- activated
每次组件渲染的时候,都会执行 beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
在 keep-alive 缓存的组件被激活的时候,都会执行 activated 钩子
activated(){
this.getData() // 获取数据
},
四、$nextTick
五、生命周期
六、路由懒加载
七、Vuex 的用法
八、封装 Http 请求
九、按需加载 UI 组件
十、自定义指令 & 过滤器
十一、methods & computed & watch 的用法及区别
十二、组件间通信
十三、render 函数之 JSX 用法
十四、Vue 单元测试
💯 🚀