前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue3源码阅读笔记之组件是如何实现

Vue3源码阅读笔记之组件是如何实现

原创
作者头像
wanyicheng
修改2021-04-14 10:10:39
1.4K0
修改2021-04-14 10:10:39
举报
文章被收录于专栏:纸上得来终觉浅
代码语言:javascript
复制
// vue是如何把一个type是一个组件配置对象的vnode变成实际可以被插入到dom树中的vnode的呢?
/**
 * 我们一般会这样声明一个root组件 
 * {
 *  data() {
 *  ... 
 *  },
 *  mounted() {...},
 *  template: '<div class="xxx">...</div>'
 * }
 * 
 * 而实际可以直接插入的对应vnode结构会有一个组件实例存储用户的钩子函数,同时有一个render函数控制生成template对应的dom结构
 * 下面来看vue的实现:
 * 
 */

// 实例的mount方法使用的render就是来自这里
const render = (vnode, container) => {
    if (vnode == null) {
        if (container._vnode) {
            unmount(container._vnode, null, null, true);
        }
    }
    else {
        // 直接调用patch 把vnode的内容当做补丁打到实际的dom跟节点上
        patch(container._vnode || null, vnode, container);
    }
    flushPostFlushCbs();
    container._vnode = vnode;
};

// 来自与全局render对象的 patch 方法实现了vnode的转化

// 把n2 vnode映射到实际dom(父节点 container 插入锚点anchor之前)中
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
    // n1为之前状态的vnode            
    // patching & not same type, unmount old tree
    // 之前存在过vnode节点 说明这是一次更新操作
    // 如果2次节点类型不同 说明可以直接卸载之前的vnode了
    if (n1 && !isSameVNodeType(n1, n2)) {
        // 更新待插入的锚点位置
        anchor = getNextHostNode(n1);
        // 卸载n1
        unmount(n1, parentComponent, parentSuspense, true);
        // 置空
        n1 = null;
    }
    // -2 子内容没有动态内容 不需要再进行diff对比了
    if (n2.patchFlag === -2 /* BAIL */) {
        // 不进行优化
        optimized = false;
        n2.dynamicChildren = null;
    }
    // 初始化的时候直接走这里
    const { type, ref, shapeFlag } = n2;
    switch (type) {
        // text node 已确定的元素类型vnode 直接调用底层方法
        case Text:
            processText(n1, n2, container, anchor);
            break;
        // 注释 node 已确定的元素类型vnode 直接调用底层方法
        case Comment:
            processCommentNode(n1, n2, container, anchor);
            break;
        // 直接插入html 如v-html的内容 也是直接调用底层方法
        case Static:
            if (n1 == null) {
                mountStaticNode(n2, container, anchor, isSVG);
            }
            else {
                patchStaticNode(n1, n2, container, isSVG);
            }
            break;
        // 文档片段 由于含有多个vnode 所以需要单独调用 processFragment
        case Fragment:
            processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            break;
        default:
            // 元素vnode (如普通dom节点 div等)
            if (shapeFlag & 1 /* ELEMENT */) {
                processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            }
            // 4 | 2 说明目前这个vnode还是用的组件对象作为标签 无法直接处理 需要再转化
            else if (shapeFlag & 6 /* COMPONENT */) {
                processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            }
            // TELEPORT 组件采用自己的 process 方法处理patch 见后文
            else if (shapeFlag & 64 /* TELEPORT */) {
                type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
            }
            // SUSPENSE 同上
            else if (shapeFlag & 128 /* SUSPENSE */) {
                type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
            }
            else {
                warn('Invalid VNode type:', type, `(${typeof type})`);
            }
    }
    // set ref
    // 在当前组件或者元素的patch结束之后 已经完成实际的dom更新了
    // 这时候可以更新父组件所持有的ref信息了
    if (ref != null && parentComponent) {
        setRef(ref, n1 && n1.ref, parentSuspense, n2);
    }
};

// 主要看 processComponent  方法:

// 处理组件
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
    if (n1 == null) {
        if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
            parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
        }
        else {
            // 初始化走这里 挂载组件到根目标节点
            mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
        }
    }
    else {
        updateComponent(n1, n2, optimized);
    }
};

// 再看 mountComponent

// 挂载组件
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
    // 先得到这个组件对应的组件实例
    const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
    console.log('initialVNode.component instance: ', instance);
    if (instance.type.__hmrId) {
        registerHMR(instance);
    }
    {
        pushWarningContext(initialVNode);
        startMeasure(instance, `mount`);
    }
    // inject renderer internals for keepAlive
    if (isKeepAlive(initialVNode)) {
        instance.ctx.renderer = internals;
    }
    // resolve props and slots for setup context
    {
        startMeasure(instance, `init`);
    }
    // 设置实例的 props 和 attrs slot 属性
    setupComponent(instance);
    {
        endMeasure(instance, `init`);
    }
    // setup() is async. This component relies on async logic to be resolved
    // before proceeding
    if (instance.asyncDep) {
        parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect);
        // Give it a placeholder if this is not hydration
        // TODO handle self-defined fallback
        if (!initialVNode.el) {
            const placeholder = (instance.subTree = createVNode(Comment));
            processCommentNode(null, placeholder, container, anchor);
        }
        return;
    }
    setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
    {
        popWarningContext();
        endMeasure(instance, `mount`);
    }
};

// 主要看三个方法 1. createComponentInstance 2. setupComponent 3. setupRenderEffect

const emptyAppContext = createAppContext();
let uid$2 = 0;
function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    // inherit parent app context - or - if root, adopt from root vnode
    // 所有组件共享一个app上下文
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    // 组件实例对象
    // 因为组件内部的状态需要维持 用户设置的变量和方法都需要维持
    const instance = {
        uid: uid$2++,
        vnode,
        type,
        parent,
        appContext,
        root: null,
        next: null,
        // vnode子树
        subTree: null,
        // 控制更新的effect函数
        update: null,
        // render函数
        render: null,
        // 这个代理很关键 我们再钩子函数业务函数中的this其实就是它
        proxy: null,
        exposed: null,
        withProxy: null,
        effects: null,
        provides: parent ? parent.provides : Object.create(appContext.provides),
        accessCache: null,
        renderCache: [],
        // local resovled assets
        components: null,
        directives: null,
        // resolved props and emits options
        propsOptions: normalizePropsOptions(type, appContext),
        emitsOptions: normalizeEmitsOptions(type, appContext),
        // emit
        emit: null,
        emitted: null,
        // state
        // 几个跟数据有关的字段
        ctx: EMPTY_OBJ,
        data: EMPTY_OBJ,
        props: EMPTY_OBJ,
        attrs: EMPTY_OBJ,
        slots: EMPTY_OBJ,
        refs: EMPTY_OBJ,
        setupState: EMPTY_OBJ,
        setupContext: null,
        // suspense related
        suspense,
        suspenseId: suspense ? suspense.pendingId : 0,
        asyncDep: null,
        asyncResolved: false,
        // lifecycle hooks
        // not using enums here because it results in computed properties
        isMounted: false,
        isUnmounted: false,
        isDeactivated: false,
        bc: null,
        c: null,
        bm: null,
        m: null,
        bu: null,
        u: null,
        um: null,
        bum: null,
        da: null,
        a: null,
        rtg: null,
        rtc: null,
        ec: null
    };
    {
        // ctx是一个数据仓库 上面放了内置默认的实例方法 以及后面用户的data数据prop数据都会一一导入到这个仓库中
        instance.ctx = createRenderContext(instance);
    }
    // 绑定root
    instance.root = parent ? parent.root : instance;
    // 实例的$emit方法
    instance.emit = emit.bind(null, instance);
    return instance;
}

// 属性很多 不用一次性全部弄清楚 分析某个细节功能的时候再单独跟踪某个属性就好了 先看下 createRenderContext

function createRenderContext(instance) {
    const target = {};
    // expose internal instance for proxy handlers
    Object.defineProperty(target, `_`, {
        configurable: true,
        enumerable: false,
        get: () => instance
    });
    // expose public properties
    Object.keys(publicPropertiesMap).forEach(key => {
        Object.defineProperty(target, key, {
            configurable: true,
            enumerable: false,
            get: () => publicPropertiesMap[key](instance),
            // intercepted by the proxy so no need for implementation,
            // but needed to prevent set errors
            set: NOOP
        });
    });
    // expose global properties
    const { globalProperties } = instance.appContext.config;
    Object.keys(globalProperties).forEach(key => {
        Object.defineProperty(target, key, {
            configurable: true,
            enumerable: false,
            get: () => globalProperties[key],
            set: NOOP
        });
    });
    return target;
}

const getPublicInstance = (i) => {
    if (!i)
        return null;
    if (isStatefulComponent(i))
        return i.exposed ? i.exposed : i.proxy;
    return getPublicInstance(i.parent);
};
const publicPropertiesMap = extend(Object.create(null), {
    $: i => i,
    $el: i => i.vnode.el,
    $data: i => i.data,
    $props: i => (shallowReadonly(i.props) ),
    $attrs: i => (shallowReadonly(i.attrs) ),
    $slots: i => (shallowReadonly(i.slots) ),
    $refs: i => (shallowReadonly(i.refs) ),
    $parent: i => getPublicInstance(i.parent),
    $root: i => getPublicInstance(i.root),
    $emit: i => i.emit,
    $options: i => (resolveMergedOptions(i) ),
    $forceUpdate: i => () => queueJob(i.update),
    $nextTick: i => nextTick.bind(i.proxy),
    $watch: i => (instanceWatch.bind(i) )
});

// 可以看到ctx上被挂载了的内置默认实例方法

function setupComponent(instance, isSSR = false) {
    isInSSRComponentSetup = isSSR;
    const { props, children } = instance.vnode;
    const isStateful = isStatefulComponent(instance);
    initProps(instance, props, isStateful, isSSR);
    initSlots(instance, children);
    const setupResult = isStateful
        ? setupStatefulComponent(instance, isSSR)
        : undefined;
    isInSSRComponentSetup = false;
    return setupResult;
}

// 主要看这个 setupStatefulComponent

function setupStatefulComponent(instance, isSSR) {
    const Component = instance.type;
    {
        if (Component.name) {
            validateComponentName(Component.name, instance.appContext.config);
        }
        if (Component.components) {
            const names = Object.keys(Component.components);
            for (let i = 0; i < names.length; i++) {
                validateComponentName(names[i], instance.appContext.config);
            }
        }
        if (Component.directives) {
            const names = Object.keys(Component.directives);
            for (let i = 0; i < names.length; i++) {
                validateDirectiveName(names[i]);
            }
        }
    }
    // 0. create render proxy property access cache
    instance.accessCache = Object.create(null);
    // 1. create public instance / render proxy
    // also mark it raw so it's never observed
    // 创建ctx的代理
    instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
    {
        // 导入props的属性到实例上
        exposePropsOnRenderContext(instance);
    }
    // 2. call setup()
    const { setup } = Component;
    // 可以看到 有setup选项的就以它的结果为准 不再执行finishComponentSetup了
    if (setup) {
        const setupContext = (instance.setupContext =
            setup.length > 1 ? createSetupContext(instance) : null);
        currentInstance = instance;
        pauseTracking();
        // 执行setup方法
        const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props) , setupContext]);
        resetTracking();
        currentInstance = null;
        // 根据返回类型处理
        // promise代表异步组件
        if (isPromise(setupResult)) {
            if (isSSR) {
                // return the promise so server-renderer can wait on it
                return setupResult.then((resolvedResult) => {
                    // 以后单独分析setup的时候再说
                    handleSetupResult(instance, resolvedResult);
                });
            }
            else {
                // async setup returned Promise.
                // bail here and wait for re-entry.
                // 异步标记
                instance.asyncDep = setupResult;
            }
        }
        else {
            handleSetupResult(instance, setupResult);
        }
    }
    else {
        finishComponentSetup(instance);
    }
}

// 控制实例上的属性handlers 主要是注意取值和赋值的优先级
const PublicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
        // let @vue/reactivity know it should never observe Vue public instances.
        if (key === "__v_skip" /* SKIP */) {
            return true;
        }
        // for internal formatters to know that this is a Vue instance
        if (key === '__isVue') {
            return true;
        }
        // data / props / ctx
        // This getter gets called for every property access on the render context
        // during render and is a major hotspot. The most expensive part of this
        // is the multiple hasOwn() calls. It's much faster to do a simple property
        // access on a plain object, so we use an accessCache object (with null
        // prototype) to memoize what access type a key corresponds to.
        let normalizedProps;
        if (key[0] !== '$') {
            const n = accessCache[key];
            if (n !== undefined) {
                switch (n) {
                    case 0 /* SETUP */:
                        return setupState[key];
                    case 1 /* DATA */:
                        return data[key];
                    case 3 /* CONTEXT */:
                        return ctx[key];
                    case 2 /* PROPS */:
                        return props[key];
                    // default: just fallthrough
                }
            }
            else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
                accessCache[key] = 0 /* SETUP */;
                return setupState[key];
            }
            else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
                accessCache[key] = 1 /* DATA */;
                return data[key];
            }
            else if (
            // only cache other properties when instance has declared (thus stable)
            // props
            (normalizedProps = instance.propsOptions[0]) &&
                hasOwn(normalizedProps, key)) {
                accessCache[key] = 2 /* PROPS */;
                return props[key];
            }
            else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
                accessCache[key] = 3 /* CONTEXT */;
                return ctx[key];
            }
            else if (!isInBeforeCreate) {
                accessCache[key] = 4 /* OTHER */;
            }
        }
        const publicGetter = publicPropertiesMap[key];
        let cssModule, globalProperties;
        // public $xxx properties
        if (publicGetter) {
            if (key === '$attrs') {
                track(instance, "get" /* GET */, key);
                markAttrsAccessed();
            }
            return publicGetter(instance);
        }
        else if (
        // css module (injected by vue-loader)
        (cssModule = type.__cssModules) &&
            (cssModule = cssModule[key])) {
            return cssModule;
        }
        else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
            // user may set custom properties to `this` that start with `$`
            accessCache[key] = 3 /* CONTEXT */;
            return ctx[key];
        }
        else if (
        // global properties
        ((globalProperties = appContext.config.globalProperties),
            hasOwn(globalProperties, key))) {
            return globalProperties[key];
        }
        else if (currentRenderingInstance &&
            (!isString(key) ||
                // #1091 avoid internal isRef/isVNode checks on component instance leading
                // to infinite warning loop
                key.indexOf('__v') !== 0)) {
            if (data !== EMPTY_OBJ &&
                (key[0] === '$' || key[0] === '_') &&
                hasOwn(data, key)) {
                warn(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved ` +
                    `character ("$" or "_") and is not proxied on the render context.`);
            }
            else if (instance === currentRenderingInstance) {
                warn(`Property ${JSON.stringify(key)} was accessed during render ` +
                    `but is not defined on instance.`);
            }
        }
    },
    set({ _: instance }, key, value) {
        const { data, setupState, ctx } = instance;
        if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
            setupState[key] = value;
        }
        else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
            data[key] = value;
        }
        else if (hasOwn(instance.props, key)) {
            warn(`Attempting to mutate prop "${key}". Props are readonly.`, instance);
            return false;
        }
        if (key[0] === '$' && key.slice(1) in instance) {
            warn(`Attempting to mutate public property "${key}". ` +
                    `Properties starting with $ are reserved and readonly.`, instance);
            return false;
        }
        else {
            if (key in instance.appContext.config.globalProperties) {
                Object.defineProperty(ctx, key, {
                    enumerable: true,
                    configurable: true,
                    value
                });
            }
            else {
                ctx[key] = value;
            }
        }
        return true;
    },
    has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {
        let normalizedProps;
        return (accessCache[key] !== undefined ||
            (data !== EMPTY_OBJ && hasOwn(data, key)) ||
            (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
            ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
            hasOwn(ctx, key) ||
            hasOwn(publicPropertiesMap, key) ||
            hasOwn(appContext.config.globalProperties, key));
    }
};

function exposePropsOnRenderContext(instance) {
    const { ctx, propsOptions: [propsOptions] } = instance;
    if (propsOptions) {
        Object.keys(propsOptions).forEach(key => {
            Object.defineProperty(ctx, key, {
                enumerable: true,
                configurable: true,
                get: () => instance.props[key],
                set: NOOP
            });
        });
    }
}

// 重点看 finishComponentSetup

function finishComponentSetup(instance, isSSR) {
    const Component = instance.type;
    // template / render function normalization
    if (!instance.render) {
        // could be set from setup()
        if (compile && Component.template && !Component.render) {
            {
                startMeasure(instance, `compile`);
            }
            // 在这里编译模板得到render函数
            // 在这里经过了模板编译 得到template对应的vnode结构 生成了render函数 详细实现后面再分析
            // 目前只需要知道它调用可以得到一个vnode  这个vnode包含了用户设置的信息 可以被patch直接使用产生各类dom元素就好了
            Component.render = compile(Component.template, {
                isCustomElement: instance.appContext.config.isCustomElement,
                delimiters: Component.delimiters
            });
            {
                endMeasure(instance, `compile`);
            }
        }
        instance.render = (Component.render || NOOP);
        // for runtime-compiled render functions using `with` blocks, the render
        // proxy used needs a different `has` handler which is more performant and
        // also only allows a whitelist of globals to fallthrough.
        if (instance.render._rc) {
            instance.withProxy = new Proxy(instance.ctx, RuntimeCompiledPublicInstanceProxyHandlers);
        }
    }
    // support for 2.x options
    {
        currentInstance = instance;
        pauseTracking();
        // 这里就设置用户选项的地方了
        applyOptions(instance, Component);
        resetTracking();
        currentInstance = null;
    }
    // warn missing template/render
    if (!Component.render && instance.render === NOOP) {
        /* istanbul ignore if */
        if (!compile && Component.template) {
            warn(`Component provided template option but ` +
                `runtime compilation is not supported in this build of Vue.` +
                (` Use "vue.global.js" instead.`
                            ) /* should not happen */);
        }
        else {
            warn(`Component is missing template or render function.`);
        }
    }
}

// 重点看这里 applyOptions

let isInBeforeCreate = false;
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 就是proxy
    const publicThis = instance.proxy;
    const ctx = instance.ctx;
    // 来自于app上下文的mixins
    const globalMixins = instance.appContext.mixins;
    if (asMixin && render && instance.render === NOOP) {
        instance.render = render;
    }
    // applyOptions is called non-as-mixin once per instance
    // 钩子 beforeCreate
    if (!asMixin) {
        isInBeforeCreate = true;
        callSyncHook('beforeCreate', "bc" /* BEFORE_CREATE */, options, instance, globalMixins);
        isInBeforeCreate = false;
        // global mixins are applied first
        applyMixins(instance, globalMixins, deferredData, deferredWatch, deferredProvide);
    }
    // extending a base component...
    if (extendsOptions) {
        applyOptions(instance, extendsOptions, deferredData, deferredWatch, deferredProvide, true);
    }
    // local mixins
    if (mixins) {
        applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide);
    }
    const checkDuplicateProperties = createDuplicateChecker() ;
    {
        const [propsOptions] = instance.propsOptions;
        if (propsOptions) {
            for (const key in propsOptions) {
                checkDuplicateProperties("Props" /* PROPS */, key);
            }
        }
    }
    // options initialization order (to be consistent with Vue 2):
    // - props (already done outside of this function)
    // - inject
    // - methods
    // - data (deferred since it relies on `this` access)
    // - computed
    // - watch (deferred since it relies on `this` access)

    // 处理inject
    if (injectOptions) {
        if (isArray(injectOptions)) {
            for (let i = 0; i < injectOptions.length; i++) {
                const key = injectOptions[i];
                // 注入inject的字段 从 provide中
                ctx[key] = inject(key);
                {
                    checkDuplicateProperties("Inject" /* INJECT */, key);
                }
            }
        }
        else {
            for (const key in injectOptions) {
                const opt = injectOptions[key];
                if (isObject(opt)) {
                    ctx[key] = inject(opt.from || key, opt.default, true /* treat default function as factory */);
                }
                else {
                    ctx[key] = inject(opt);
                }
                {
                    checkDuplicateProperties("Inject" /* INJECT */, key);
                }
            }
        }
    }
    // 用户的方法
    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, {
                        // 注意这里的this publicThis
                        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));
        }
        // 处理data函数
        if (dataOptions) {
            // @ts-ignore dataOptions is not fully type safe
            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);
    }
    // 前文分析过的computed
    if (computedOptions) {
        for (const key in computedOptions) {
            const opt = computedOptions[key];
            const get = isFunction(opt)
                ? opt.bind(publicThis, publicThis)
                : isFunction(opt.get)
                    ? opt.get.bind(publicThis, publicThis)
                    : NOOP;
            if (get === NOOP) {
                warn(`Computed property "${key}" has no getter.`);
            }
            const set = !isFunction(opt) && isFunction(opt.set)
                ? opt.set.bind(publicThis)
                : () => {
                        warn(`Write operation failed: computed property "${key}" is readonly.`);
                    }
                    ;
            const c = computed$1({
                get,
                set
            });
            Object.defineProperty(ctx, key, {
                enumerable: true,
                configurable: true,
                get: () => c.value,
                set: v => (c.value = v)
            });
            {
                checkDuplicateProperties("Computed" /* COMPUTED */, key);
            }
        }
    }
    // watch字段
    if (watchOptions) {
        deferredWatch.push(watchOptions);
    }
    if (!asMixin && deferredWatch.length) {
        deferredWatch.forEach(watchOptions => {
            for (const key in watchOptions) {
                createWatcher(watchOptions[key], ctx, publicThis, key);
            }
        });
    }
    if (provideOptions) {
        deferredProvide.push(provideOptions);
    }
    // 组件实例的provide会被子组件所继承 然后任意子组件可以inject注入其中的字段
    if (!asMixin && deferredProvide.length) {
        deferredProvide.forEach(provideOptions => {
            const provides = isFunction(provideOptions)
                ? provideOptions.call(publicThis)
                : provideOptions;
            Reflect.ownKeys(provides).forEach(key => {
                provide(key, provides[key]);
            });
        });
    }
    // asset options.
    // To reduce memory usage, only components with mixins or extends will have
    // resolved asset registry attached to instance.
    if (asMixin) {
        if (components) {
            extend(instance.components ||
                (instance.components = extend({}, instance.type.components)), components);
        }
        if (directives) {
            extend(instance.directives ||
                (instance.directives = extend({}, instance.type.directives)), directives);
        }
    }
    // lifecycle options
    if (!asMixin) {
        callSyncHook('created', "c" /* CREATED */, options, instance, globalMixins);
    }
    // 下面的方法基本上都是给实例注入对应的用户钩子函数 存入对应的钩子数组中 到了指定了阶段执行就好了
    if (beforeMount) {
        onBeforeMount(beforeMount.bind(publicThis));
    }
    if (mounted) {
        onMounted(mounted.bind(publicThis));
    }
    if (beforeUpdate) {
        onBeforeUpdate(beforeUpdate.bind(publicThis));
    }
    if (updated) {
        onUpdated(updated.bind(publicThis));
    }
    if (activated) {
        onActivated(activated.bind(publicThis));
    }
    if (deactivated) {
        onDeactivated(deactivated.bind(publicThis));
    }
    if (errorCaptured) {
        onErrorCaptured(errorCaptured.bind(publicThis));
    }
    if (renderTracked) {
        onRenderTracked(renderTracked.bind(publicThis));
    }
    if (renderTriggered) {
        onRenderTriggered(renderTriggered.bind(publicThis));
    }
    if (beforeDestroy) {
        warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`);
    }
    if (beforeUnmount) {
        onBeforeUnmount(beforeUnmount.bind(publicThis));
    }
    if (destroyed) {
        warn(`\`destroyed\` has been renamed to \`unmounted\`.`);
    }
    if (unmounted) {
        onUnmounted(unmounted.bind(publicThis));
    }
    if (isArray(expose)) {
        if (!asMixin) {
            if (expose.length) {
                const exposed = instance.exposed || (instance.exposed = proxyRefs({}));
                expose.forEach(key => {
                    exposed[key] = toRef(publicThis, key);
                });
            }
            else if (!instance.exposed) {
                instance.exposed = EMPTY_OBJ;
            }
        }
        else {
            warn(`The \`expose\` option is ignored when used in mixins.`);
        }
    }
}

// 经过上面这么多的过程 我们终于看到了平常用到的this是什么 同时我们设置的对象的选项参数都是怎么被挂载到vue的组件实例上的

// 最后再看 setupRenderEffect

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
    // create reactive effect for rendering
    // 每个组件实例有一个update方法 在属性变化的是调用 更新vnode 和 dom视图
    // 前文分析过 effect得到一个包装函数 内置的方法在依赖源发生变化后会执行 而componentEffect方法中有调用render导致了依赖收集
    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
            // vnode也是钩子函数的
            if ((vnodeHook = props && props.onVnodeBeforeMount)) {
                invokeVNodeHook(vnodeHook, parent, initialVNode);
            }
            // render
            {
                startMeasure(instance, `render`);
            }
            // 在这里 我们得到了render产生的vnode子树
            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`);
                }
                // 最终测试的vnode子树 可以被patch所接受 它具备的信息足够支撑渲染出我们想要的dom结构了
                patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
                {
                    endMeasure(instance, `patch`);
                }
                initialVNode.el = subTree.el;
            }
            // mounted hook
            // 钩子
            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 render
            // 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 {
            // 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);
            }
            // render
            {
                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) );
};

// 再看下 renderComponentRoot

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 for
            // runtime-compiled render functions using `with` block.
            // 这里很重要 调用render得到vnode子树 完成依赖收集
            const proxyToUse = withProxy || proxy;
            result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
            // 组件可以继承在它身上加的属性(出去已经被prop和emit声明的之外) 都会被处理放在attrs字段中
            fallthroughAttrs = attrs;
        }
        else {
            // 先不看
            // functional
            const render = Component;
            // in dev, mark attrs accessed if optional props (attrs === props)
            if (true && attrs === props) {
                markAttrsAccessed();
            }
            result = normalizeVNode(render.length > 1
                ? render(props, true
                    ? {
                        get attrs() {
                            markAttrsAccessed();
                            return attrs;
                        },
                        slots,
                        emit
                    }
                    : { attrs, slots, emit })
                : render(props, null /* we know it doesn't need it */));
            fallthroughAttrs = Component.props
                ? attrs
                : getFunctionalFallthrough(attrs);
        }
        // attr merging
        // in dev mode, comments are preserved, and it's possible for a template
        // to have comments along side the root element which makes it a fragment
        let root = result;
        let setRoot = undefined;
        // 一种特殊情况 片段结构中 多个子节点 只有一个是有效的元素 其余都是注释 这种情况
        if (true &&
            result.patchFlag > 0 &&
            result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {
            ;
            [root, setRoot] = getChildRoot(result);
        }
        // 组件的继承属性实现 对应文档中详细的描述
        if (Component.inheritAttrs !== false && fallthroughAttrs) {
            const keys = Object.keys(fallthroughAttrs);
            const { shapeFlag } = root;
            if (keys.length) {
                if (shapeFlag & 1 /* ELEMENT */ ||
                    shapeFlag & 6 /* COMPONENT */) {
                    if (propsOptions && keys.some(isModelListener)) {
                        // If a v-model listener (onUpdate:xxx) has a corresponding declared
                        // prop, it indicates this component expects to handle v-model and
                        // it should not fallthrough.
                        // related: #1543, #1643, #1989
                        fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions);
                    }
                    // 属性继承到vnode中
                    root = cloneVNode(root, fallthroughAttrs);
                }
                // render过程中如果有元素显示获取$attrs accessedAttrs这个值在handler的get就会被置位
                else if (true && !accessedAttrs && root.type !== Comment) {
                    const allAttrs = Object.keys(attrs);
                    const eventAttrs = [];
                    const extraAttrs = [];
                    for (let i = 0, l = allAttrs.length; i < l; i++) {
                        const key = allAttrs[i];
                        if (isOn(key)) {
                            // ignore v-model handlers when they fail to fallthrough
                            if (!isModelListener(key)) {
                                // remove `on`, lowercase first letter to reflect event casing
                                // accurately
                                eventAttrs.push(key[2].toLowerCase() + key.slice(3));
                            }
                        }
                        else {
                            extraAttrs.push(key);
                        }
                    }
                    // 对应文档中的警告信息
                    if (extraAttrs.length) {
                        warn(`Extraneous non-props attributes (` +
                            `${extraAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes.`);
                    }
                    if (eventAttrs.length) {
                        warn(`Extraneous non-emits event listeners (` +
                            `${eventAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes. ` +
                            `If the listener is intended to be a component custom event listener only, ` +
                            `declare it using the "emits" option.`);
                    }
                }
            }
        }
        // inherit directives
        if (vnode.dirs) {
            if (true && !isElementRoot(root)) {
                warn(`Runtime directive used on component with non-element root node. ` +
                    `The directives will not function as intended.`);
            }
            root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;
        }
        // inherit transition data
        if (vnode.transition) {
            if (true && !isElementRoot(root)) {
                warn(`Component inside <Transition> renders non-element root node ` +
                    `that cannot be animated.`);
            }
            root.transition = vnode.transition;
        }
        if (true && setRoot) {
            setRoot(root);
        }
        else {
            result = root;
        }
    }
    catch (err) {
        handleError(err, instance, 1 /* RENDER_FUNCTION */);
        result = createVNode(Comment);
    }
    currentRenderingInstance = null;
    return result;
}

// 好了,有了完整的组件实例和对应的vnode信息了,已经可以准备把我们写的template对应的vnode通过patch映射到实际的dom中了。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档