Vue.js核心源码解析

196 阅读5分钟

目标

  • 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++;
})

微信截图_20220313122533.png

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

  1. 倒序遍历,第一次更新了ab, 会保留i的进度,第二次遍历会把e1,e2的位置进行改变,发现不同就立马推出,或者遍历完最短的列表

例子:old : a b (c d)

new:a b e f (c d)

remain need handle e f

  1. common sequence + mount 比较旧列表短于新列表,新列表中间有新节点,或者首部,或者尾部有新节点,进行比较,所以新的节点,会mount上去

(a b)
(a b) c

(a b) c
(a b)

  1. common sequence + unmount,如果是旧列表有多余的旧节点,就会把它unmount下来

    (a b) c
    (a b)

    a (b c)
    (b c)

  2. 经过前两次循环,首尾相同的节点都已跳过, 记录新列表的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--;
                    }
                }
            }
        }
    };