目标
- Vue3核心源码解读解析
知识要点
Vue3独立的响应式包
reactive:把复杂的(object,array)等类型数据并返回响应式数据proxy
ref:把基本(number,string)等类型数据并返回响应式数据proxy
effect(fn):监听数据变化的处理函数
过程: render=>VNode =>patch =>mount
初始化时,执行fn,收集依赖,渲染页面;响应式数据更新,effect重新执行fn=>更新=>渲染页面
使用代码
const state = reactive({count:1})
const number = ref(0)
effect(()=>{
const count = state.count;
console.log(count)
number++;
})
reactive代码主要逻辑:
-
如果是只读数据直接返回,或者已经在proxy里面已经有代理数据,直接找到返回
-
新的数,创建 const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers),把proxy放进proxyMap
-
ProxyHandler:
- get:createGetter // 收集依赖*
- set //发布订阅,触发更新
- deleteProperty
- has
- ownKeys
createGetter会进行依赖收集
-
处理数组,调用内置方法,而非原型链上的方法,进行返回结果
- 重写数组,直接重写方法,规避多次触发 get/set 问题
-
处理对象,说白了就是递归 reactive,但并不是一开始就递归所有key => reactive,只有当获取某一个key时,才 reactive*
-
收集对象依赖,if (!isReadonly) { track(target, TrackOpTypes.GET, key) }
createSetter
-
根据shallow标志处理,处理对应 ref 数据的情况 ,返回结果
-
shallow为true
- 判断key是新加的,还是修改的,调用Refect.set(target,key,value,receiver)修改值
- receiver 为 Proxy 或者继承 Proxy 的对象,这里需要处理原型链的情况,因为如果原型链继承的也是一个proxy,通过Reflect.set修改原型链上的属性会触发两次setter
- 调用trigger 触发 effect的fn 执行
effect
- 初始化执行,fn , 包装createReactiveEffect(fn)=> activeEffect
- fn 执行,触发get ---》track收集依赖
track 收集依赖
- let depsMap = targetMap.get(target), 如果depsMap为空,就把depsMap关联起来
- let dep = depsMap.get(key),key为target的属性,如果dep为空,就set 进key, dep=new Set()关联起来
- dep.add(activeEffect); 把之前创建的activeEffect,收集起来 收集当前激活的effect作为依赖
- activeEffect.deps.push(dep) *当前激活的effect,收集dep作为依赖,当前effect内部有可能触发其他effect
trigger 触发更新,派发通知
- const depsMap = targetMap.get(target)获取到对应数据原始数据的依赖集合
- 创建需要运行的effect集合 const effects = new Set()
- 定义遍历添加effect的函数 add
- 触发了对应的操作,修改、删除、添加,都添加effect依赖到effects中
- 定义执行函数const run
- effects.forEach(run); //遍历执行effect
Ref
- 创建ref数据,处理基本类型数据的监听createRef(value)
- createRef-> 创建ref new RefImpl(rawValue, shallow)
- 通过设置.value的情况,来触发整个track的过程
- 调用RefImpl里面的get value, set value
vue3响应化小demo
```
const count = ref(0);
effect(function(){
console.log(count.value)
})
count.value++;
let activeEffect;
function ref(init){
return new RefImpl(init)
}
class RefImpl{
constructor(init){
this._value=init;
}
get value(){
trackRefValue(this)
return this._value;
}
set value(newVal){
this._value=newVal;
triggerRefValue(this)
}
}
function trackRefValue(refValue){
if(refValue.dep){
refValue.dep = new Set()
}
refValue.dep.add(activeEffect)
}
function triggerRefValue(refValue){
[...refValue.dep].forEach(effect=>effect.fn())
}
function effect(fn){
activeEffect = new ActiveEffect(fn)
fn();
}
class ActiveEffect{
constructor(fn){
this.fn=fn;
}
}
Vue3 diff算法总结
入口patchKeyedChildren 有key的时候会进入到这个方法
1.会把较短的列表先遍历完,从头开始,比较对应的节点,直到遇到不同的节点,马上退出,或者短的节点遍历完成
例子:old : (a,b) c
new:(a,b)d e
- 倒序遍历,第一次更新了ab, 会保留i的进度,第二次遍历会把e1,e2的位置进行改变,发现不同就立马推出,或者遍历完最短的列表
例子:old : a b (c d)
new:a b e f (c d)
remain need handle e f
- common sequence + mount 比较旧列表短于新列表,新列表中间有新节点,或者首部,或者尾部有新节点,进行比较,所以新的节点,会mount上去
(a b)
(a b) c
(a b) c
(a b)
-
common sequence + unmount,如果是旧列表有多余的旧节点,就会把它unmount下来
(a b) c
(a b)a (b c)
(b c) -
经过前两次循环,首尾相同的节点都已跳过, 记录新列表的unknown sequence的,有key的索引
a b [c d e] f g
a b [e d c h] f g
5.1 将新列表中具有 key 属性的节点的 key 与索引存起来备用5.2 循环遍历old的 c d e列表,找到match的新的node, 或者移除旧的node
- 新列表比旧的短,移除节点
- 旧节点有 key,就去新列表的 key-index 缓存中取该 key 对应的位置 找newIndex
- 旧节点没有 key,就从新的列表中找到满足 isSameVNodeType 的节点位置 找 newIndex
- 新的列表中,不存在与旧节点 key 一样的节点位置,卸载节点, newIndex没有
- 在新列表中找到了旧列表当前节点 【key 一致】或【没有key但类型一致】 的位置
- 最后一步,移动move 新旧同样的节点,但顺序不一致的节点 和mount 新的节点
diff 算法源码
const patchKeyedChildren = (c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
let i = 0;
const l2 = c2.length;
let e1 = c1.length - 1; // prev ending index
let e2 = l2 - 1; // next ending index
// 1. sync from start
// (a b) c
// (a b) d e
//会把较短的列表先遍历完,从头开始,比较对应的节点,直到遇到不同的节点,马上退出,或者短的节点遍历完成
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i]));
//如果是相同的节点,只需要定向去更新proxy,等,不需要去重复创建vnode
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else {
//不一样马上跳出
//
// i = 2,e1 = 2,e2 = 3 当前位置标记如下
// (a b) [c]
// (a b) [d] e
break;
}
i++;
}
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) { // 倒序遍历,第一次更新了ab, 会保留i的进度,第二次遍历会把e1,e2的位置进行改变,
// 发现不同就立马推出,或者遍历完最短的列表
const n1 = c1[e1];
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2])
: normalizeVNode(c2[e2]));
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else {
// 综合前两次循环,可将形如下面的列表 e1 = 3, e2 = 5
// a b c d
// a b e f c d
// 处理成 (.为下标 i 的位置 i = 2,e1 = 1, e2 = 3):
// a b . c d
// a b . e f c d
// 第一轮,更新了ab,第二轮更新了cd,目前还剩 ef 待插入
break;
}
e1--;
e2--;
}
// 3. common sequence + mount 比较旧列表短于新列表,新列表中间有新节点,或者首部,或者尾部有新节点,进行比较,所以新的节点,会mount上去
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) { // --------------coordinate24,coordinate25
if (i <= e2) {
const nextPos = e2 + 1; // 4
const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor; // 4 < 6 ? c.el : parentAnchor
while (i <= e2) { // 2 <= 3, c2[2] = e
patch(null, (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
i++; // i = 3, i <= 3, c2[i] = f
}
}
}
// 4. common sequence + unmount,如果是旧列表有多余的旧节点,就会把它unmount下来
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) { // 删除操作,以源码注释理解即可,略
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true);
i++;
}
}
// --------------coordinate25,coordinate26
// 5. unknown sequence 乱序
// 经过前两次循环,首尾相同的节点都已跳过, 记录新列表的unknown sequence的,有key的索引
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
else {
const s1 = i; // prev starting index
const s2 = i; // next starting index
// 5.1 build key:index map for newChildren
const keyToNewIndexMap = new Map();
// [c d e]
// [e d c h]
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i]));
if (nextChild.key != null) {
if (keyToNewIndexMap.has(nextChild.key)) {
warn$1(`Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.`);
}
// 新旧列表是存在相同节点的,将新列表中具有 key 属性的节点的 key 与索引存起来备用
// --------------coordinate26,coordinate27
keyToNewIndexMap.set(nextChild.key, i);
}
}
// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
let j;
let patched = 0;
const toBePatched = e2 - s2 + 1; // 将要更新的节点总数为 4
let moved = false;
// used to track whether any node has moved
let maxNewIndexSoFar = 0;
// works as Map<newIndex, oldIndex>
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched);
for (i = 0; i < toBePatched; i++)
newIndexToOldIndexMap[i] = 0; // [0, 0, 0, 0]
for (i = s1; i <= e1; i++) { // i = s1 = 2, e1 = 4, e2 = 5
// 遍历 [c d e]
// a b [c d e] f g
// a b [e d c h] f g
const prevChild = c1[i]; // c
if (patched >= toBePatched) { // toBePatched <= 0,新列表比旧的短,移除节点
// all new children have been patched so this can only be a removal
unmount(prevChild, parentComponent, parentSuspense, true);
continue;
}
let newIndex;
if (prevChild.key != null) { // 旧节点有 key,就去新列表的 key-index 缓存中取该 key 对应的位置
newIndex = keyToNewIndexMap.get(prevChild.key);
}
else { // 旧节点没有 key,就从新的列表中找到满足 isSameVNodeType 的节点位置
// key-less node, try to locate a key-less node of the same type
for (j = s2; j <= e2; j++) { // e2 = 5,j = 2, 3, 4, 5; s2 = 2
if (newIndexToOldIndexMap[j - s2] === 0 && // j - s2 = 0, 1, 2, 3
isSameVNodeType(prevChild, c2[j])) {
newIndex = j;
break;
}
}
}
if (newIndex === undefined) { // 新的列表中,不存在与旧节点 key 一样的节点位置,卸载节点
unmount(prevChild, parentComponent, parentSuspense, true);
}
else { // 在新列表中找到了旧列表当前节点 【key 一致】或【没有key但类型一致】 的位置
newIndexToOldIndexMap[newIndex - s2] = i + 1;
// newIndex = 4
// maxNewIndexSoFar = 0
// newIndexToOldIndexMap = [0, 0, 3, 0]
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex; // maxNewIndexSoFar = 4
}
else {
moved = true;
}
patch(prevChild, c2[newIndex], container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
patched++;
}
}
// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR;
j = increasingNewIndexSequence.length - 1;
// looping backwards so that we can use last patched node as anchor
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i;
const nextChild = c2[nextIndex];
const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(null, nextChild, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else if (moved) {
// move if:
// There is no stable subsequence (e.g. a reverse)
// OR current node is not among the stable sequence
if (j < 0 || i !== increasingNewIndexSequence[j]) {
// --------------coordinate27,coordinate28
move(nextChild, container, anchor, 2 /* REORDER */);
}
else {
j--;
}
}
}
}
};