专栏首页纸上得来终觉浅Vue3源码阅读笔记之整体执行顺序简介(2)
原创

Vue3源码阅读笔记之整体执行顺序简介(2)

好了,上面的render终于得到了,再继续看 applyOptions:

function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {
        const { 
        // composition
        mixins, extends: extendsOptions, 
        // state
        data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, 
        // assets
        components, directives, 
        // lifecycle
        beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, 
        // public API
        expose } = options;
        // 上面的信息表示了我们可以在跟组件中设置的字段以及生命周期钩子

        // 我们平常用的钩子函数中的this就是下面这个哦
        const publicThis = instance.proxy;
        const ctx = instance.ctx;

        ...
        // 设置方法到组件实例上
        if (methods) {
            for (const key in methods) {
                const methodHandler = methods[key];
                if (isFunction(methodHandler)) {
                    // In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
                    // and those are read-only but reconfigurable, so it needs to be redefined here
                    {
                        Object.defineProperty(ctx, key, {
                            value: methodHandler.bind(publicThis),
                            configurable: true,
                            enumerable: true,
                            writable: true
                        });
                    }
                    {
                        checkDuplicateProperties("Methods" /* METHODS */, key);
                    }
                }
                else {
                    warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
                        `Did you reference the function correctly?`);
                }
            }
        }
        if (!asMixin) {
            if (deferredData.length) {
                deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis));
            }
            if (dataOptions) {
                // @ts-ignore dataOptions is not fully type safe
                // 设置data 单独分析
                resolveData(instance, dataOptions, publicThis);
            }
            {
                const rawData = toRaw(instance.data);
                // 把我们配置的根组件的data返回的字段 同步到 ctx上
                for (const key in rawData) {
                    checkDuplicateProperties("Data" /* DATA */, key);
                    // expose data on ctx during dev
                    if (key[0] !== '$' && key[0] !== '_') {
                        Object.defineProperty(ctx, key, {
                            configurable: true,
                            enumerable: true,
                            get: () => rawData[key],
                            set: NOOP
                        });
                    }
                }
            }
        }
        else if (dataOptions) {
            deferredData.push(dataOptions);
        }

        ...
        // 也执行了beforeCreate生命周期钩子
        // 注册对应钩子函数 同时绑定了 this
        if (beforeMount) {
            onBeforeMount(beforeMount.bind(publicThis));
        }
        if (mounted) {
            onMounted(mounted.bind(publicThis));
        }
        ...
    }

我们这里mounted里面是有值的:

function injectHook(type, hook, target = currentInstance, prepend = false) {
        if (target) {
            // 其实就是把我们设置的方法存入数组中 保存在组件实例的 type 字段中
            const hooks = target[type] || (target[type] = []);
            // cache the error handling wrapper for injected hooks so the same hook
            // can be properly deduped by the scheduler. "__weh" stands for "with erro
            // handling".
            const wrappedHook = hook.__weh ||
                (hook.__weh = (...args) => {
                    if (target.isUnmounted) {
                        return;
                    }
                    // disable tracking inside all lifecycle hooks
                    // since they can potentially be called inside effects.
                    pauseTracking();
                    // Set currentInstance during hook invocation.
                    // This assumes the hook does not synchronously trigger other hooks, which
                    // can only be false when the user does something really funky.
                    setCurrentInstance(target);
                    const res = callWithAsyncErrorHandling(hook, target, type, args);
                    setCurrentInstance(null);
                    resetTracking();
                    return res;
                });
            if (prepend) {
                hooks.unshift(wrappedHook);
            }
            else {
                hooks.push(wrappedHook);
            }
            return wrappedHook;
        }
        else {
            const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''));
            warn(`${apiName} is called when there is no active component instance to be ` +
                `associated with. ` +
                `Lifecycle injection APIs can only be used during execution of setup().` +
                (` If you are using async setup(), make sure to register lifecycle ` +
                        `hooks before the first await statement.`
                    ));
        }
    }
    const createHook = (lifecycle) => (hook, target = currentInstance) => 
    // post-create lifecycle registrations are noops during SSR
    !isInSSRComponentSetup && injectHook(lifecycle, hook, target);
    const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */);
    const onMounted = createHook("m" /* MOUNTED */);
    const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */);
    const onUpdated = createHook("u" /* UPDATED */);
    const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */);
    const onUnmounted = createHook("um" /* UNMOUNTED */);
    const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */);
    const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */);
    const onErrorCaptured = (hook, target = currentInstance) => {
        injectHook("ec" /* ERROR_CAPTURED */, hook, target);
    };

接下来仔细看 resolveData :

function resolveData(instance, dataFn, publicThis) {
        if (!isFunction(dataFn)) {
            warn(`The data option must be a function. ` +
                `Plain object usage is no longer supported.`);
        }
        const data = dataFn.call(publicThis, publicThis);
        if (isPromise(data)) {
            warn(`data() returned a Promise - note data() cannot be async; If you ` +
                `intend to perform data fetching before component renders, use ` +
                `async setup() + <Suspense>.`);
        }
        if (!isObject(data)) {
            warn(`data() should return an object.`);
        }
        else if (instance.data === EMPTY_OBJ) {
            // 其实就是这里 把 组件实例的data变成响应式的 
            instance.data = reactive(data);
        }
        else {
            // existing data: this is a mixin or extends.
            extend(instance.data, data);
        }
    }

来看下 reactive :

function reactive(target) {
        // if trying to observe a readonly proxy, return the readonly version.
        if (target && target["__v_isReadonly" /* IS_READONLY */]) {
            return target;
        }
        return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
    }

暂且先不看常规handlers和集合类型的handlers的具体代理内容

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
        if (!isObject(target)) {
            {
                console.warn(`value cannot be made reactive: ${String(target)}`);
            }
            return target;
        }
        // target is already a Proxy, return it.
        // exception: calling readonly() on a reactive object
        if (target["__v_raw" /* RAW */] &&
            !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
            return target;
        }
        // target already has corresponding Proxy
        const proxyMap = isReadonly ? readonlyMap : reactiveMap;
        const existingProxy = proxyMap.get(target);
        if (existingProxy) {
            return existingProxy;
        }
        // only a whitelist of value types can be observed.
        const targetType = getTargetType(target);
        if (targetType === 0 /* INVALID */) {
            return target;
        }
        // 设置代理 并且返回这个代理
        const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
        proxyMap.set(target, proxy);
        return proxy;
    }

可以看到,目前只是直接对组件实例的data做了一次代理,handlers在普通对象情况下为 baseHandlers

至此,applyOptions的作用大致清楚了,就是把我们设置的对象的属性一一映射到组件实例上,同时把组件实例的data变成代理,注册各类钩子函数到实例上等等

setupComponent方法结束后,再看 setupRenderEffect:

function createDevEffectOptions(instance) {
        return {
            scheduler: queueJob,
            allowRecurse: true,
            onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
            onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
        };
    }

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
        // create reactive effect for rendering
        // 创建一个 effect 函数 它同时也是数据发送改变后再次触发的 effect 函数
        instance.update = effect(function componentEffect() {
            if (!instance.isMounted) {
                let vnodeHook;
                const { el, props } = initialVNode;
                const { bm, m, parent } = instance;
                // beforeMount hook
                if (bm) {
                    invokeArrayFns(bm);
                }
                // onVnodeBeforeMount
                if ((vnodeHook = props && props.onVnodeBeforeMount)) {
                    invokeVNodeHook(vnodeHook, parent, initialVNode);
                }
                // rende
                {
                    startMeasure(instance, `render`);
                }
                // 得到可以被渲染的子DOM树
                const subTree = (instance.subTree = renderComponentRoot(instance));
                {
                    endMeasure(instance, `render`);
                }
                if (el && hydrateNode) {
                    {
                        startMeasure(instance, `hydrate`);
                    }
                    // vnode has adopted host node - perform hydration instead of mount.
                    hydrateNode(initialVNode.el, subTree, instance, parentSuspense);
                    {
                        endMeasure(instance, `hydrate`);
                    }
                }
                else {
                    {
                        startMeasure(instance, `patch`);
                    }
                    // 再次调用path
                    patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
                    {
                        endMeasure(instance, `patch`);
                    }
                    initialVNode.el = subTree.el;
                }
                // mounted hook
                // mounted钩子执行
                if (m) {
                    queuePostRenderEffect(m, parentSuspense);
                }
                // onVnodeMounted
                if ((vnodeHook = props && props.onVnodeMounted)) {
                    const scopedInitialVNode = initialVNode;
                    queuePostRenderEffect(() => {
                        invokeVNodeHook(vnodeHook, parent, scopedInitialVNode);
                    }, parentSuspense);
                }
                // activated hook for keep-alive roots.
                // #1742 activated hook must be accessed after first rende
                // since the hook may be injected by a child keep-alive
                const { a } = instance;
                if (a &&
                    initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
                    queuePostRenderEffect(a, parentSuspense);
                }
                instance.isMounted = true;
                {
                    devtoolsComponentAdded(instance);
                }
                // #2458: deference mount-only object parameters to prevent memleaks
                initialVNode = container = anchor = null;
            }
            else {
                ...
            }
        }, createDevEffectOptions(instance) );
    };

我们先看effect函数:

function effect(fn, options = EMPTY_OBJ) {
        if (isEffect(fn)) {
            fn = fn.raw;
        }
        const effect = createReactiveEffect(fn, options);
        // 我们的函数会马上执行一次
        if (!options.lazy) {
            effect();
        }
        return effect;
    }

let uid = 0;
    function createReactiveEffect(fn, options) {
        const effect = function reactiveEffect() {
            if (!effect.active) {
                return options.scheduler ? undefined : fn();
            }
            if (!effectStack.includes(effect)) {
                cleanup(effect);
                try {
                    enableTracking();
                    effectStack.push(effect);
                    // activeEffect赋值 在render函数调用过程中 它就是变量被修改后触发的effect
                    activeEffect = effect;
                    return fn();
                }
                finally {
                    effectStack.pop();
                    resetTracking();
                    activeEffect = effectStack[effectStack.length - 1];
                }
            }
        };
        effect.id = uid++;
        effect.allowRecurse = !!options.allowRecurse;
        effect._isEffect = true;
        effect.active = true;
        effect.raw = fn;
        effect.deps = [];
        effect.options = options;
        return effect;
    }

从上面可以看到我们在effect内部定义的函数被包裹后会执行一次:

先重点看下 renderComponentRoot(instance) :

function renderComponentRoot(instance) {
        const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
        let result;
        currentRenderingInstance = instance;
        {
            accessedAttrs = false;
        }
        try {
            let fallthroughAttrs;
            if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
                // withProxy is a proxy with a different `has` trap only fo
                // runtime-compiled render functions using `with` block.
                const proxyToUse = withProxy || proxy;
                // 主要就是这行 指定this后执行render得到数据被取值后的vnode树 在这个过程中 由于之前设置的proxy代理 劫持了get和set 导致当前effect会被收集到counter的effect集合中
                // 最后渲染得到的是一个简单的字符串 然后normalizeVNode把字符串变成一个 textnode类型的vnode 供path方法使用
                result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
                fallthroughAttrs = attrs;
            }
            else {
                ...
            }
            ...
        }
        catch (err) {
            handleError(err, instance, 1 /* RENDER_FUNCTION */);
            result = createVNode(Comment);
        }
        currentRenderingInstance = null;
        return result;
    }

简单看下 proxy的handlers做了什么:

const mutableHandlers = {
        get,
        set,
        deleteProperty,
        has,
        ownKeys
    };

const get = /*#__PURE__*/ createGetter();

function createGetter(isReadonly = false, shallow = false) {
        return function get(target, key, receiver) {
            if (key === "__v_isReactive" /* IS_REACTIVE */) {
                return !isReadonly;
            }
            else if (key === "__v_isReadonly" /* IS_READONLY */) {
                return isReadonly;
            }
            else if (key === "__v_raw" /* RAW */ &&
                receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)) {
                return target;
            }
            const targetIsArray = isArray(target);
            if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
                return Reflect.get(arrayInstrumentations, key, receiver);
            }
            const res = Reflect.get(target, key, receiver);
            if (isSymbol(key)
                ? builtInSymbols.has(key)
                : isNonTrackableKeys(key)) {
                return res;
            }
            if (!isReadonly) {
                // render函数调用时 这里会触发
                track(target, "get" /* GET */, key);
            }
            if (shallow) {
                return res;
            }
            if (isRef(res)) {
                // ref unwrapping - does not apply for Array + integer key.
                const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
                return shouldUnwrap ? res.value : res;
            }
            if (isObject(res)) {
                // Convert returned value into a proxy as well. we do the isObject check
                // here to avoid invalid value warning. Also need to lazy access readonly
                // and reactive here to avoid circular dependency.
                // 初始化过程中只是简单的代理了一层 如果对象下面字段也是对象 推迟到get触发后再设置代理
                return isReadonly ? readonly(res) : reactive(res);
            }
            return res;
        };
    }

function track(target, type, key) {
        if (!shouldTrack || activeEffect === undefined) {
            return;
        }
        let depsMap = targetMap.get(target);
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map()));
        }
        let dep = depsMap.get(key);
        if (!dep) {
            depsMap.set(key, (dep = new Set()));
        }
        if (!dep.has(activeEffect)) {
            dep.add(activeEffect);
            activeEffect.deps.push(dep);
            if (activeEffect.options.onTrack) {
                activeEffect.options.onTrack({
                    effect: activeEffect,
                    target,
                    type,
                    key
                });
            }
        }
    }

可以看到effect是如何而被收集起来且被收集到那去了。在赋值修改组件实例的字段时,它还会被派上用场的

在得到新的vonde子树并且完成依赖收集后,可以看 path 了,我们再次碰到它了,之前由于type是个组件类型导致它走了其他逻辑,现在它是 textnode类型了:

// type = Symbol(Text) 
const { type, ref, shapeFlag } = n2;
            switch (type) {
                case Text:
                    processText(n1, n2, container, anchor);
                    break;

// n2 vnode节点的children就是 " Counter: 0" 已经被之前处理过了 可以直接插入了
const processText = (n1, n2, container, anchor) => {
            if (n1 == null) {
                hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
            }
            else {
                const el = (n2.el = n1.el);
                if (n2.children !== n1.children) {
                    hostSetText(el, n2.children);
                }
            }
        };   

好了,我们终于看到真实也是最终的dom操作了。至此,静态插入内容已经走完,接下来看动态修改的部分。

// mounted hook
// mounted钩子执行
if (m) {
    queuePostRenderEffect(m, parentSuspense);
}

之前看到setupRenderEffect中在path完之后,调用了m钩子,执行了我们设置的方法;

setTimeout(() => {
    this.counter++
}, 1000)

这个this就是绑定了 组件实例上的proxy字段,而对它上面任何一个字段的修改都会触发handlers的set:

const set = /*#__PURE__*/ createSetter();

function createSetter(shallow = false) {
        return function set(target, key, value, receiver) {
            const oldValue = target[key];
            if (!shallow) {
                value = toRaw(value);
                if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                    oldValue.value = value;
                    return true;
                }
            }
            const hadKey = isArray(target) && isIntegerKey(key)
                ? Number(key) < target.length
                : hasOwn(target, key);
            const result = Reflect.set(target, key, value, receiver);
            // don't trigger if target is something up in the prototype chain of original
            if (target === toRaw(receiver)) {
                if (!hadKey) {
                    trigger(target, "add" /* ADD */, key, value);
                }
                else if (hasChanged(value, oldValue)) {
                    // 触发trigge
                    trigger(target, "set" /* SET */, key, value, oldValue);
                }
            }
            return result;
        };
    }

function trigger(target, type, key, newValue, oldValue, oldTarget) {
        const depsMap = targetMap.get(target);
        if (!depsMap) {
            // never been tracked
            return;
        }
        const effects = new Set();
        const add = (effectsToAdd) => {
            if (effectsToAdd) {
                effectsToAdd.forEach(effect => {
                    if (effect !== activeEffect || effect.allowRecurse) {
                        effects.add(effect);
                    }
                });
            }
        };
        if (type === "clear" /* CLEAR */) {
            // collection being cleared
            // trigger all effects for target
            depsMap.forEach(add);
        }
        else if (key === 'length' && isArray(target)) {
            depsMap.forEach((dep, key) => {
                if (key === 'length' || key >= newValue) {
                    add(dep);
                }
            });
        }
        else {
            // schedule runs for SET | ADD | DELETE
            if (key !== void 0) {
                add(depsMap.get(key));
            }
            // also run for iteration key on ADD | DELETE | Map.SET
            switch (type) {
                case "add" /* ADD */:
                    if (!isArray(target)) {
                        add(depsMap.get(ITERATE_KEY));
                        if (isMap(target)) {
                            add(depsMap.get(MAP_KEY_ITERATE_KEY));
                        }
                    }
                    else if (isIntegerKey(key)) {
                        // new index added to array -> length changes
                        add(depsMap.get('length'));
                    }
                    break;
                case "delete" /* DELETE */:
                    if (!isArray(target)) {
                        add(depsMap.get(ITERATE_KEY));
                        if (isMap(target)) {
                            add(depsMap.get(MAP_KEY_ITERATE_KEY));
                        }
                    }
                    break;
                case "set" /* SET */:
                    if (isMap(target)) {
                        add(depsMap.get(ITERATE_KEY));
                    }
                    break;
            }
        }
        const run = (effect) => {
            if (effect.options.onTrigger) {
                effect.options.onTrigger({
                    effect,
                    target,
                    key,
                    type,
                    newValue,
                    oldValue,
                    oldTarget
                });
            }
            // rendereffect的是设置了 scheduler 的 
            if (effect.options.scheduler) {
                effect.options.scheduler(effect);
            }
            else {
                effect();
            }
        };
        effects.forEach(run);
    }

看起来挺多,简单总结一下就是取出之前每个观察对象的effect集合,按照字段分别取,然后依次执行它们,目前我们就 一个rendereffect,先看下 scheduler 被设置了啥:

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
            // create reactive effect for rendering
            instance.update = effect(function componentEffect() {
                if (!instance.isMounted) {
                    ...
                }
                else {
                    // updateComponent
                    // This is triggered by mutation of component's own state (next: null)
                    // OR parent calling processComponent (next: VNode)
                    let { next, bu, u, parent, vnode } = instance;
                    let originNext = next;
                    let vnodeHook;
                    {
                        pushWarningContext(next || instance.vnode);
                    }
                    if (next) {
                        next.el = vnode.el;
                        updateComponentPreRender(instance, next, optimized);
                    }
                    else {
                        next = vnode;
                    }
                    // beforeUpdate hook
                    if (bu) {
                        invokeArrayFns(bu);
                    }
                    // onVnodeBeforeUpdate
                    if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
                        invokeVNodeHook(vnodeHook, parent, next, vnode);
                    }
                    // rende
                    {
                        startMeasure(instance, `render`);
                    }
                    const nextTree = renderComponentRoot(instance);
                    {
                        endMeasure(instance, `render`);
                    }
                    const prevTree = instance.subTree;
                    instance.subTree = nextTree;
                    {
                        startMeasure(instance, `patch`);
                    }
                    patch(prevTree, nextTree, 
                    // parent may have changed if it's in a teleport
                    hostParentNode(prevTree.el), 
                    // anchor may have changed if it's in a fragment
                    getNextHostNode(prevTree), instance, parentSuspense, isSVG);
                    {
                        endMeasure(instance, `patch`);
                    }
                    next.el = nextTree.el;
                    if (originNext === null) {
                        // self-triggered update. In case of HOC, update parent component
                        // vnode el. HOC is indicated by parent instance's subTree pointing
                        // to child component's vnode
                        updateHOCHostEl(instance, nextTree.el);
                    }
                    // updated hook
                    if (u) {
                        queuePostRenderEffect(u, parentSuspense);
                    }
                    // onVnodeUpdated
                    if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
                        queuePostRenderEffect(() => {
                            invokeVNodeHook(vnodeHook, parent, next, vnode);
                        }, parentSuspense);
                    }
                    {
                        devtoolsComponentUpdated(instance);
                    }
                    {
                        popWarningContext();
                    }
                }
            }, createDevEffectOptions(instance) );
        };

createDevEffectOptions(instance) :

function createDevEffectOptions(instance) {
        return {
            scheduler: queueJob,
            allowRecurse: true,
            onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
            onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
        };
    }

function queueJob(job) {
        // the dedupe search uses the startIndex argument of Array.includes()
        // by default the search index includes the current job that is being run
        // so it cannot recursively trigger itself again.
        // if the job is a watch() callback, the search will start with a +1 index to
        // allow it recursively trigger itself - it is the user's responsibility to
        // ensure it doesn't end up in an infinite loop.
        if ((!queue.length ||
            !queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
            job !== currentPreFlushParentJob) {
            const pos = findInsertionIndex(job);
            if (pos > -1) {
                queue.splice(pos, 0, job);
            }
            else {
                queue.push(job);
            }
            queueFlush();
        }
    }

function queueFlush() {
        if (!isFlushing && !isFlushPending) {
            isFlushPending = true;
            currentFlushPromise = resolvedPromise.then(flushJobs);
        }
    }

const getId = (job) => job.id == null ? Infinity : job.id;
    function flushJobs(seen) {
        isFlushPending = false;
        isFlushing = true;
        {
            seen = seen || new Map();
        }
        flushPreFlushCbs(seen);
        // Sort queue before flush.
        // This ensures that:
        // 1. Components are updated from parent to child. (because parent is always
        //    created before the child so its render effect will have smalle
        //    priority number)
        // 2. If a component is unmounted during a parent component's update,
        //    its update can be skipped.
        queue.sort((a, b) => getId(a) - getId(b));
        try {
            for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
                const job = queue[flushIndex];
                if (job) {
                    if (true) {
                        checkRecursiveUpdates(seen, job);
                    }
                    callWithErrorHandling(job, null, 14 /* SCHEDULER */);
                }
            }
        }
        finally {
            flushIndex = 0;
            queue.length = 0;
            flushPostFlushCbs(seen);
            isFlushing = false;
            currentFlushPromise = null;
            // some postFlushCb queued jobs!
            // keep flushing until it drains.
            if (queue.length || pendingPostFlushCbs.length) {
                flushJobs(seen);
            }
        }
    }

简单总结一下:插入待处理队列中,按照id从小到大排序,然后在then下个tick中执行

而在此执行的effect走的是 mounted已经是true的情况:

主要工作也是2个:

renderComponentRoot(instance)

patch(prevTree, nextTree, 
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el), 
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree), instance, parentSuspense, isSVG);

renderComponentRoot由于我们没改变dom结构,在此调用得到的也是一个textnode只不过它的children值变了

还是主要看patch:

const { type, ref, shapeFlag } = n2;
        switch (type) {
            case Text:
                processText(n1, n2, container, anchor);
                break;
跟之前一样的逻辑路线:
const processText = (n1, n2, container, anchor) => {
            if (n1 == null) {
                hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
            }
            else {
                // 走这里
                const el = (n2.el = n1.el);
                if (n2.children !== n1.children) {
                    hostSetText(el, n2.children);
                }
            }
        };

只不过由于n1前vnode节点有值,所以是修改路线。

其实每个节点都会初次插入以及修改赋值2种情况,然后每个根据节点类型做的处理也不同,我们目前看到的只是最简单的一种。

好了,至此更新的过程也过了一遍。

总结一下:其实和Vue文档中关于生命周期状态的图一样,Vue为了实现前文所述的效果做了很多很多工作。我们按照最简单的情况简要的分析了一下主体流程,在其他文章再逐步分析其他的一些复杂场景。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue3源码阅读笔记之整体执行顺序简介(1)

    从Vue官网得到源码(https://unpkg.com/vue@next),拷贝到本地文件,然后创建如下html:

    wanyicheng
  • Vue3源码解析 —— 编译器(一)入口

    Vue3 发布已经有一段时间了,最近也有机会在公司项目中用上了 Vue3 + TypeScript + Vite 的技术栈,所以闲暇之余抽空也在抽空阅读 Vue...

    Originalee
  • Vue3 深度解析

    距离尤雨溪首次公开 Vue3 (vue-next)源码有一个多月了。青笔观察到,刚发布国庆期间,出现不少解读 Vue3 源码的文章。当然不少有追风蹭热之嫌,文章...

    我是一条小青蛇
  • Vue3 源码解析(三):静态提升

    Vue3 尚未发布正式版本前,尤大在一次关于 Vue3 的分享中提及了静态提升,当时笔者就对这个亮点产生了好奇,所以在源码阅读时,静态提升也是笔者的一个重点阅读...

    Originalee
  • Vue3 源码解析(三):静态提升

    Vue3 尚未发布正式版本前,尤大在一次关于 Vue3 的分享中提及了静态提升,当时笔者就对这个亮点产生了好奇,所以在源码阅读时,静态提升也是笔者的一个重点阅读...

    Originalee
  • 从零开始构建 vue3

    2019年10月5日凌晨,Vue 的作者尤雨溪公布了 Vue3 的源代码。当然,它暂时还不是完整的 Vue3,而是 pre-alpha 版,只完成了一些核心功能...

    我是一条小青蛇
  • Vue3发布半年我不学,摸鱼爽歪歪,哎~就是玩儿

    是从 Vue 2 开始学基础还是直接学 Vue 3 ?尤雨溪给出的答案是:“直接学 Vue 3 就行了,基础概念是一模一样的。”

    程序员十三
  • Vue3 源码解析(六):响应式原理与 reactive

    今天这篇文章是笔者会带着大家一起深入剖析 Vue3 的响应式原理实现,以及在响应式基础 API 中的 reactive 是如何实现的。对于 Vue 框架来说,其...

    Originalee
  • 写给初中级前端的高级进阶指南(JS、TS、Vue、React、性能、学习规划)

    我曾经一度很迷茫,在学了Vue、React的实战开发和应用以后,好像遇到了一些瓶颈,不知道该怎样继续深入下去。相信这也是很多一两年经验的前端工程师所遇到共同问题...

    ssh_晨曦时梦见兮

扫码关注云+社区

领取腾讯云代金券