Vue2
特征
- 更好的性能
- 更小的包体积
- 更好的TypeScript集成
- 更优秀的API设计
MVVM模型
MVC是Model-View-Controller的简称 是在前期被使用非常多的一种架构模式 比如ios 其实在前端里面 - html -> view 页面时图 **- javaScript -> controller **从服务器获取到数据
- model -> 取到的数据 然后通过controller 来返回给view
MVVM 是 Model-View-ViewModel 的简称 是目前非常流行的架构模式 ViewModel vue 就当个中间商 来承接 Model 和 View
VNode(虚拟节点)
vue 无论组件还是元素 最终表现的都是一个个VNode 多个VNode会将组成VNode Tree(虚拟DOM/也就是JS对象) 需要DOM的作用
- 跨平台性 1.可以渲染到真实DOM -> 渲染到浏览器上
- 移动端的一些控件(button/view/image)->移动原生控件在移动端显示出来
- 桌面端一些控件
- VR设备的空间
- DIFF算法 在vue中 对于相同父元素的子节点发生了改变 并不会重新渲染整个列表 只需要操作更新的Vnode就行 如果没有设置key的话 :vue会使用一种最大限度减少动态元素 并且尽可能的尝试就地修改/复用相同类型元素的算法 在第一次虚拟DOM和最新的虚拟DOM做对比 它会找到有变化的Vnode的第一个的位置 之后的所有的Vnode都会重新改变 渲染。 在源码对比中是patchUnkeyedChildren方法 如果有设置key的话 :它会基于key的变化重新排列元素顺序 并且会移除/销毁key不存在的元素 在第一次虚拟DOM和最新的虚拟DOM做对比 它可以根据key的值来对比 这样会更快的 更高效的寻找插入的Vnode 从而在真实DOM中 只渲染这个改变的key 在源码中是patchKeyedChildren 他的查找规则
- 从头部开始遍历 遇到相同节点就继续 遇到不同节点就跳出循环
- 从尾部开始遍历 遇见相同节点就继续 遇见不同节点就跳出循环
- 如果都遍历完了,依然还有新的节点 那么新的节点就做新增操作 (新节点多 就新增)
- 如果都遍历完了,依旧还有旧的节点 那么旧的节点就做删除操作 (旧节点多 就删除)
- 如果中间存在不知道如何排列的位置序列 那么就使用key建立索引图 最大限度的使用旧节点(就是通过key来判断中间的是否有相同的key 如果有就复用旧节点 反之则进行删除/新增操作)vue的diff特色 处理未知的或者乱序的节点
data属性
在vue2的时候 可以传入一个对象(官方推荐是一个函数) 在vue3的时候 必须传入一个函数
methods
为什么不可以使用箭头函数? 不使用箭头函数 this的指向是什么呢? 1.因为我们将要在methods中使用data返回对象的数据 所以说这个this必须得有值(vue里面this手动绑定了当前组件的实例) 2.因为如果使用了箭头函数 他的this查找规则是自己的上层作用域来查找this 他的上层找到了是一window 3.使用普通函数可以找到this 因为在vue源码中 它先获取到组件实例 然后判断是否有methods属性 然后便利methods 取出来每一个函数 并且手动绑定了bind函数 帮组件实例传入进去当参数
v-model
- 本质:他就是input v-bind绑定value属性的值;v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
- 修饰符
- lazy 它会把input的input事件切换成change事件 只有提交的时(回车时)才会触发
- number 绑定一个number数值 并且如果是一个string类型的话 在可以转换的时候 进行隐式转换
- trim 自动过滤用户输入的空白字符
插槽
默认插槽:这个默认内容只会在美欧提供插入内容的时候现实 具名插槽:给插槽制定对应名字 然后希望达到插槽对应的现实
<slot name='left'></slot>
<template v-slot:left></template>
动态插槽名
<template v-slot:[dataName]></template>
data(){
return {
name:'left'
}
}
具名插槽的缩写
<template #left></template>
作用域插槽 :可以访问到子组件中的内容 过程:
- 在父组件定好数据
- 通过组件slot传递给子组件
- 子组件遍历数据
- 子组件定义插槽prop
- 通过v-slot:default方式获取到子组件定义的prop
- 使用slotProps中的item和index
provide和inject
一些深度嵌套的组件 子组件想要获取父组件的内容 在这种情况下,如果我们仍然将props沿着组件链逐级传递下 去,就会非常的麻烦; 无论层级结构有多深,父组件都可以作为其所有子组件的依赖 提供者;
- 父组件有一个 provide 选项来提供数据;
- 子组件有一个 inject 选项来开始使用这些数据;
- 父组件不需要知道哪些子组件使用它 provide 的 property
- 子组件不需要知道 inject 的 property 来自哪里
provide (){
return {
name:this.name
}
}
这样写 是吧 data 的 name 值绑定给 provide 属于值绑定值 当 data 里面的 name 发生改变的话 provide 不会改变活着直接传入this
provide (){
return {
name:this
}
}
这时候就需要引入 import { computed} from 'vue'
provide (){
return {
name:computed()=>{
return this.message
}
}
}
其他组件使用时候
inject :['name']
全局事件总线
vue3从实例中移除了off $once方法 如果我还要希望使用全局事件总线 就需要通过第三方库 Vue3官方有推荐一些库,例如 mitt 或 tiny-emitter;
通过事件总线eventBus 来实现跨组件监听事件 当组件被销毁的时候 需要移除监听
可以使用mitt库
事件发送
eventBus.emit('click','why',12,15)
事件监听
created (){
eventBus.on('click',this.eventFun
}
methods:{
eventFun(name,age){
console.log(name,age)
}
}
事件移除
unmounted:{
eventBus.off('click',this.eventFun)
}
生命周期钩子函数
概念:每一个组件都会经历创建 挂在 更新 卸载等一些列操作
- 创建home组件
- 将组件挂在到组件树上(虚拟DOM上)
- 修改data中的数据 组件被更新
- 组件卸载
根据vue官方生命周期的流程图解析 当我们创建了组件的时候 也就是app=Vue.createApp(options) app.mount(el)
- 初始化vue 内部自己初始化工作 (也就是内部自己的准备工作)
- beforeCreate vue打算创建组件了 可以做一些创建之前的操作
- created 组件实例已经被创建好了 但是还没有挂在到虚拟DOM上面 因为它只是在创建实例 但是没有对里面的模版进行编译 组件对象本身会有个render函数 他会返回组件内部自身的vnode节点这个时候他就会判断是否有 template 如果有template的话 他会创建对应的vnode 如果没有 它会编译里面el对应的东西的innerHtml 如果在el里面还有render函数的话 他也会执行render函数
- beforeMount 准备去吧Vnode挂在到虚拟DOM上
- mounted 已经把Vnode挂在到了虚拟DOM上面 这个时候会根据特定的算法 挂在道真实DOM上 用户已经可以看到所有元素了
- beforeUpdate 当挂在上的数据发生了改变 他会先回调beforeUdate函数 然后根据改变的新生成的Vnode 然后通过diff算法进行对比 然后进行更新
- updated 真实dom已经跟新修改完毕
- beforeUmount 当发现组件不再使用了 准备卸载之前回调
- umounted 已经从虚拟dom中移除掉了 然后回调这个函数
自我总结
- beforeCreate 创建组件实例
- created 拿到template 模版进行编译 因为需要拿到vnode(如果使用了 vue-loader 它会直接创建好对应的render函数 「它会吧里面的标签元素直接创建好 使用的是createVnode函数{在vue3中他是创建的createElementBlack()函数 进行了优化}」)
- beforeMount 挂在到虚拟DOM
- mounted 根据虚拟DOM生成了真实DOM -> 界面可以看到元素标签
- beforeUpdate 挂在上面的数据发生了改变
- updated 根据最新生成的新的vnode生成新的虚拟DOM 生成真实DOM
- beforeUmount 当组件不再使用 v-if=false
- unmounted 将之前挂载在虚拟DOM中的Vnode从虚拟DOM移除 将会把对应的真实DOM移除
created 一般会做 他不会获取到 dom 1.发送网络请求 2.事件监听(evebtbus)/监听数据(this.$watch()) mounted 当前元素已经被挂载了 1.可以获取到 DOM 并且使用 DOM unmounted 一般回收的操作(取消事件监听)
$refs
在 vue 中不推荐原生 DOM 操作 这个时候可以给组件绑定一个 refs 属性 注意事项 在同一个组件 设置了相同的ref多次使用的话 获取到的不是同一个组件实例(instance) 因为vue内部其实做了吧组件当成了一个对象的形式导出创建,其实他在内部吧对象当成了一个class类的方式来实现(因为class类每一次创建都是一个新的)这就会实现每一个相同的组件 他都是不同的组件实例 通过refs获取到的也是不同的(在react中已经把组件通过class的继承方式来的 但是vue还是按照对象模拟class的方式) 如果通过 ref 拿到子组件的元素的时候 他会获取子组件的根元素 如果子组件会有多个根组件的话 他会获取到第一个 node 节点 this.el
动态组件 component
is 来源的组件
1.全局注册的组件(app.compoents())
2.局部注册的组件
<component :is="tab[icuretndex]"></component>
tab:['home','about','']
keep alive
当前组件切换继续存活 实例不会被销毁 并且被缓存起来 include->{String|RegExp|Arry(['a','b'])} 包含 那些组件被缓存 它来源于组件定义时的name属性 它不是跟组件注册名称一致 (name第二个用处就是跟着vue-devtools有关系) exclude 不被缓存 max 最大缓存多少个组件 它vue内部有个算法 当超出最大值的时候 它会将长时间不使用的组件 移除缓存
<keep-alive include='home,about'>
</keep-alive>
对于保持keep-alive的组件 监听有没有进行切换 他有自己的生命周期
进入活跃的状态钩子函数 进入
activated(){
}
离开活跃的时候状态钩子 离开
deactivated(){
}
异步组件
dist 文件夹的目录分析
- webpack 代码分包
- app 业务代码
- chunk-vendors 第三方插件依赖的代码 都打包到这个js中
- 在dist中打包后编译的文件 后缀为js.map为映射的源代码
为什么要分包 因为vue会默认编译打包把所有的业务代码文件打包都放到静态服务器上 等页面下载的时候 虽然页面显示的是home组件 但是也得需要等当前这个文件的全部下载下来 才会执行js代码 这就回导致首屏渲染速度过慢的问题。所以我们吧组件进行拆分成小的代码文件
使用import("./main.js").then(res=>{
})
这种就是异步导入 并且可以让webpack对导入的文件进行分包处理
因为在注册组件的时候 不能把promise注册到component里面 进行注册
在业务使用的时候 vue提供了defineAsyncComponent
类型一:工厂函数,该工厂函数需要返回一个Promise对象;
类型二:接受一个对象类型,对异步函数进行配置;
1.
const AsyncComponent =defineAsyncComponent(//返回一个promise(可以使用工厂函数) ()=>{import('./main.js')})
组件上的v-model (默认的modelValue)
当返回的时候 可以修改父组件的值 <componets :modelValue='oppCounter' @update:modelValue="oppCounter=$event">
可以自定义修改传入的默认值 也可以传入多个 <componets v-model:counter='oppCounter' v-model:name='name'
Mixin 组件的混入
import messageMixin from './mixin' mixins:[messageMixin] 合并规则 1.如果都有data 默认情况是合并到一起 在data属性名字冲突 会优先使用自身组件 2.如果有都有生命周期 他会两个生命周期都会执行