Vue3源码学习总结
2022-11-17开始,2023-01-15大概结束,坚持下来了
响应式数据
怎么实现响应式数据
reactive、readonly、ref、effect和computed
响应式数据就是劫持数据的读写,get的时候收集依赖,set的时候触发依赖,这个依赖可以理解为它的上级上下文。
TODO: 对reactive、readonly、ref 写下实现
响应式数据触发视图更新
依赖收集/触发:全局的变量存储依赖
effect: 接受一个函数然后执行这个函数
跟effect结合:effect接受一个函数(fn),这个函数会被实例化成一个Effect对象,执行Effect对象的run(执行fn),一个全局的变量activeEffect的值就是当前Effect对象,如果fn里面有响应式数据,响应式数据读取的时候会触发track的过程,会将activeEffect加入到,响应式数据的依赖中,当响应式数据改变的时候,会将相应的依赖取出来,然后执行。
虚拟节点跟真实dom
虚拟节点是对真实dom的一个描述,创建虚拟节点的时候,会根据这个虚拟节点的属性创建
const vnode = {
type,
props,
children,
key: props?.key ?? null,
shapeFlag: getShapeFlag(type),
el: null,
component: null,
}
虚拟节点跟实例
虚拟节点只是描述了真实dom,具体的组件操作方法需要挂载到组件的实例上。
const instance = {
vnode,
props: {},
setupState: {},
type: vnode.type,
emit: () => {},
provides: parent ? Object.create(parent.provides) : {}, // 原型链指向构造函数,怎么指向
parent,
slots: null,
isMounted: false,
subTree: null,
// effect的返回值
update: null,
// 下一个需要更新的节点
nextVNode: null
}
实现更新
props更新
children更新
diff算法
实现
/**
老的虚拟节点对应真实的dom已经copy给新的虚拟节点的真实dom了
*/
const c1 = old.children
const c2 = old.children
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 进行左端的对比
while( i <= e1 && i <= e2 ) {
if (isSame(c1[i], c2[i])) {
//向下层递归处理
} else {
break
}
i++
}
// 进行右端对比
while( i <= e1 && i <= e2 ) {
if (isSame(c1[e1], c2[e2])) {
//向下层递归处理
} else {
break
}
e1--
e2--
}
// 对不同情况进行处理,比较 i e1 和 e2的大小关系
if (i > e2 && i < e1) {
//新的全部遍历完了,老的还没遍历完,直接删除老的
} else if ( i > e1 & i <= e2) {
//老的全部遍历完了,新的还没遍历完,创建新的节点(考虑锚点)
} else {
// 老的新的都没遍历完,要对混乱的部分进行处理,总的策略是在老的中找到尽可能可以复用的部分
1: 找到新节点在老节点中的位置
2: 根据1的结果,找出最长递增序列(相对位置保持不变的)
3: 需要考虑的情况
3.1: 老节点不存在新节点,就需要新建
3.2: 新节点混乱部分的下标跟递增序列的值中不匹配,说明需要移动
3.3: 匹配说明相对位置没有变化,跳过
}
优化点
边界条件的优化
在处理递增序列之前,如果全部是递增的就不需要进行递增序列的处理,利用之前已有的结果,判断有没有必要处理下一步。
学到了什么
1: 实现一个功能的时候,现全部实现,后续在拆分重构,没必要一开始就想的很好
2: 发现vue的实现也不是很复杂,但是各各部分之间的结合比较巧妙,比如说effect跟响应式数据的结合、虚拟节点跟实例之间的关系(存在对对方的相互引用),还有就是写代码的过程中抽离重复逻辑
3: 设计思想方面的话,模块指责划分,比如说runtime-core
和 runtime-dom
,runtime-core
只负责虚拟节点的创建更新的流程,如果想要创建dom元素的话,runtime-dom
提供对应的api注入到runtime-core
里面,类似于依赖注入。