createApp 源码解读

591 阅读2分钟

组件的创建由createApp方法实现,并由.mount("#app")挂载至页面中

var createApp = function (rootComponent) {
    var app = {
        _component: rootComponent,
        mount: function (rootContainer) {
            console.log("基于根组件创建 vnode");
            var vnode = createVNode(rootComponent);
            console.log("调用 render,基于 vnode 进行开箱");
            render(vnode, rootContainer);
        },
    };
    return app;
};

render方法来进行渲染

var render = function (vnode, container) {
    debug.mainPath("调用 patch")();
    patch(null, vnode, container);
};
function patch(n1, n2, container, parentComponent) {
    if (container === void 0) { container = null; }
    if (parentComponent === void 0) { parentComponent = null; }
    var type = n2.type, shapeFlag = n2.shapeFlag;
    switch (type) {
        case Text:
            processText(n1, n2, container);
            break;
        default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
                console.log("处理 element");
                processElement(n1, n2, container);
            }
            else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
                console.log("处理 component");
                processComponent(n1, n2, container, parentComponent);
            }
    }
}

shapeFlag的类型如下

(function (ShapeFlags) {
    ShapeFlags[ShapeFlags["ELEMENT"] = 1] = "ELEMENT";
    ShapeFlags[ShapeFlags["STATEFUL_COMPONENT"] = 4] = "STATEFUL_COMPONENT";
    ShapeFlags[ShapeFlags["TEXT_CHILDREN"] = 8] = "TEXT_CHILDREN";
    ShapeFlags[ShapeFlags["ARRAY_CHILDREN"] = 16] = "ARRAY_CHILDREN";
    ShapeFlags[ShapeFlags["SLOTS_CHILDREN"] = 32] = "SLOTS_CHILDREN";
})(ShapeFlags || (ShapeFlags = {}));

在patch方法中判断所创建对象为elemnt或者component,此时传入的n1始终为null,不论是element或者component始终都是初始化创建

Element处理

mountElement()首先会通过document.createElement 来创建element

然后判断所属的element类型,如果为文本类型,则直接渲染,否则会遍历所有子节点,再调用patch方法对子节点再次进行渲染

function mountElement(vnode, container) {
    var shapeFlag = vnode.shapeFlag, props = vnode.props;
    var el = (vnode.el = hostCreateElement(vnode.type));
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        // \u5904\u7406\u6587\u672C:转码为处理文本
        console.log("\u5904\u7406\u6587\u672C:" + vnode.children);
        hostSetElementText(el, vnode.children); // 直接渲染
    }
    else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(vnode.children, el); // 递归调用patch对子节点再次渲染
    }
    if (props) {
        for (var key in props) {
            var nextVal = props[key];
            hostPatchProp(el, key, null, nextVal);
        }
    }
    console.log("vnodeHook  -> onVnodeBeforeMount");
    console.log("DirectiveHook  -> beforeMount");
    console.log("transition  -> beforeEnter");
    hostInsert(el, container);
    console.log("vnodeHook  -> onVnodeMounted");
    console.log("DirectiveHook  -> mounted");
    console.log("transition  -> enter");
}

function mountChildren(children, container) {
    children.forEach(function (VNodeChild) {
        console.log("mountChildren:", VNodeChild);
        patch(null, VNodeChild, container);
    });
}

Component处理

mountComponent() 实现的核心是 setupComponent(),它可以分为两个过程

  • 开始安装,它会初始化 propsslots、调用 setup()、验证组件和指令的合理性。
    • 设置状态组件(动态组件:数据可变化并渲染),此时会调用 setup()
  • 结束安装,它会初始化 computeddatawatchmixin 和生命周期等等。
    • 组件未挂载,则需初始化组件(初始化isMounted 都为false,经过初始化isMounted则变为true)
    • 若已经挂载,则对组件进行更新

如果公共队列中没有该安转渲染,则会执行nextTick(延时回调)

function mountComponent(initialVNode, container, parentComponent) {
    var instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
    console.log("\u521B\u5EFA\u7EC4\u4EF6\u5B9E\u4F8B:" + instance.type.name);
    setupComponent(instance);
    setupRenderEffect(instance, container);
}
// 开始安装
function setupComponent(instance) {
    var _a = instance.vnode, 
    props = _a.props, 
    children = _a.children;
    initProps(instance, props);
    initSlots(instance, children);
    setupStatefulComponent(instance);
}
// 结束安装
function setupRenderEffect(instance, container) {
    instance.update = effect(function componentEffect() {
        if (!instance.isMounted) {
            console.log("调用 render,获取 subTree");
            var proxyToUse = instance.proxy;
            var subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
            console.log("subTree", subTree);
            console.log(instance.type.name + ":\u89E6\u53D1 beforeMount hook");
            console.log(instance.type.name + ":\u89E6\u53D1 onVnodeBeforeMount hook");
            patch(null, subTree, container, instance);
            console.log(instance.type.name + ":\u89E6\u53D1 mounted hook");
            instance.isMounted = true;
        }
        else {
            console.log("调用更新逻辑");
            var next = instance.next, vnode = instance.vnode;
            if (next) {
                next.el = vnode.el;
                updateComponentPreRender(instance, next);
            }
            var proxyToUse = instance.proxy;
            var nextTree = instance.render.call(proxyToUse, proxyToUse);
            var prevTree = instance.subTree;
            instance.subTree = nextTree;
            console.log("beforeUpdated hook");
            console.log("onVnodeBeforeUpdate hook");
            patch(prevTree, nextTree, prevTree.el, instance);
            console.log("updated hook");
            console.log("onVnodeUpdated hook");
        }
    }, {
        scheduler: function (effect) {
          // 如果公共队列中没有该安转渲染,则会执行nextTick(延时回调)
            queueJob(effect);
        },
    });
}
function queueJob(job) {
    if (!queue.includes(job)) {
        queue.push(job);
        queueFlush();
    }
}
function queueFlush() {
    if (isFlushPending)
        return;
    isFlushPending = true;
    nextTick(flushJobs);
}
function flushJobs() {
    isFlushPending = false;
    var job;
    while ((job = queue.shift())) {
        if (job) {
            job();
        }
    }
}
function nextTick(fn) {
    return fn ? p.then(fn) : p;
}

\