vue整体实现思想及mini-vue的实现
一、三大核心系统
- Compiler模块:编译模板系统
主要用来将template模板转换为render渲染函数
- Runtime模块:也可以称之为Renderer模块,真正渲染的模块
主要用来将虚拟DOM转变为真实DOM,并且渲染到浏览器页面上
主要的实现原理是利用了snabbdom的思想,具体可以看我的另一篇转载文章,点下方
虚拟DOM原理 - Reactivity模块:响应式系统
主要用来当dom发生变化的时候,通过diff算法进行更新或者替换,然后再通过渲染系统渲染到浏览器页面上
对应源码位置
那么三个系统之间如何协同工作呢?
这里借用一张coderwey老师的制图
二、实现Mini-Vue
1、描述
这里我们实现一个简洁版的Mini-Vue框架,该Vue包括三个模块
- 渲染系统模块
- 可响应式系统模块
- 应用程序入口模块
2、渲染系统实现
- 功能一:h函数,用于返回一个VNode对象
- 功能二:mount函数,用于将VNode挂载到DOM上
- 功能三:patch函数,用于对两个VNode进行对比,决定如何处理新的VNode
h函数 – 生成VNode
h函数的实现:
- 直接返回一个VNode对象即可
Mount函数 – 挂载VNode - mount函数的实现:
第一步
:根据tag,创建HTML元素,并且存储到vnode的el中;第二步
:处理props属性- 如果以on开头,那么监听事件;
- 普通属性直接通过 setAttribute 添加即可;
第三步
:处理子节点- 如果是字符串节点,那么直接设置textContent;
- 如果是数组节点,那么遍历调用 mount 函数;
Patch函数 – 对比两个VNode - patch函数的实现,分为两种情况
1、n1和n2是不同类型的节点:
- 找到n1的el父节点,删除原来的n1节点的el;
- 挂载n2节点到n1的el父节点上;
2、n1和n2节点是相同的节点:
处理props的情况
- 先将新节点的props全部挂载到el上;
- 判断旧节点的props是否不需要在新节点上,如果不需要,那么删除对应的属性;
处理children的情况
- 如果新节点是一个字符串类型,那么直接调用 el.textContent = newChildren;
- 如果新节点不同一个字符串类型:
旧节点是一个字符串类型
- 将el的textContent设置为空字符串;
- 旧节点是一个字符串类型,那么直接遍历新节点,挂载到el上;
旧节点也是一个数组类型
- 取出数组的最小长度;
- 遍历所有的节点,新节点和旧节点进行path操作;
- 如果新节点的length更长,那么剩余的新节点进行挂载操作;
- 如果旧节点的length更长,那么剩余的旧节点进行卸载操作;
3、响应式系统实现
1、依赖收集系统的实现
思想:通过集合收集依赖,首先通过某个函数将依赖添加到集合中,然后当数据发生改变的时候,调用某个函数,重新遍历集合中的每一个函数,实现更新
从上面的代码,我们可以看出,存在很多弊端,需要我们手动调用addEffect函数才能将依赖添加进去,然后数据发生改变时,需要手动调用notify函数实现更新。
那么如何让它自动添加呢?
2、响应式系统Vue2的实现
class Dep {
constructor() {
this.subscribers = new Set(); //添加一个集合
}
depend() {
//这个函数用来做依赖收集
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
//这个函数用来做通知更新
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null; //根据这个变量是否为null来决定是否收集到依赖系统中
function watchEffect(effect) {
activeEffect = effect;
effect(); //初始的时候执行一次
activeEffect = null; //收集完依赖之后再次置为空,方便添加下一个依赖
}
// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
//工具函数,根据target和key获取到对应的map
// 1.根据对象(target)取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key)