Vue3.0 - 02 - 02

130 阅读2分钟

回顾

前面分析到了 mountComponent 函数,该函数主要做了三件事:

  1. 创建组件实例 - createComponentInstance;

  2. 初始化组件 - setupComponent;

  3. 渲染组件 - setupRenderEffect。

createComponentInstance

代码在 packages/runtime-core/src/component.ts 中。组件其实就是一个对象,里面去维护了一些数据,用于渲染。这里就不多说了。

其中,有对 props 及 emits 的标准化,这个后面在介绍。


export function createComponentInstance(

    vnode: VNode,

    parent: ComponentInternalInstance | null,

    suspense: SuspenseBoundary | null

) {

    //...

    const instance: ComponentInternalInstance = {

    //...

    // Props 及 emits 标准化配置

    propsOptions: normalizePropsOptions(type, appContext),

    emitsOptions: normalizeEmitsOptions(type, appContext),

    //...

    }

    // ...

    return instance

}

setupComponent


export function setupComponent(

    instance: ComponentInternalInstance,

    isSSR = false

) {

    isInSSRComponentSetup = isSSR

    // 通过 vnode 获取 props 及 children

    const { props, children } = instance.vnode

    // 判断是否是有状态组件

    const isStateful = isStatefulComponent(instance)

    // 初始化 props 及 slots

    initProps(instance, props, isStateful, isSSR)

    initSlots(instance, children)

    // 得到 setupResult

    // 如果是有状态组件通过 setupStatefulComponent 返回

    // 反之 setupResult = undefined

    const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) :  undefined

    isInSSRComponentSetup = false

    // 返回 setupResult

    return setupResult

}

该函数返回 setupResult 对象,该对象是通过 setupStatefulComponent 函数处理返回。

此外,该函数还包括对 props 和 slots 的初始化等,这些后续在分析。

setupStatefulComponent


function setupStatefulComponent(

    instance: ComponentInternalInstance,

    isSSR: boolean

) {

    // ...

    const { setup } = Component

    if (setup) {

        const setupContext = (instance.setupContext =

        setup.length > 1 ? createSetupContext(instance) : null)

        setCurrentInstance(instance)

        pauseTracking()

        // 通过执行 setup 函数,获取执行结果

        const setupResult = callWithErrorHandling(

            setup,

            instance,

            ErrorCodes.SETUP_FUNCTION,

            [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]

        )

        resetTracking()

        unsetCurrentInstance()

        // 处理 setup 函数返回的结果

        // 如果 setup 返回的结果是 promise

        if (isPromise(setupResult)) {

            // ...

            return setupResult

            .then((resolvedResult: unknown) => {

                handleSetupResult(instance, resolvedResult, isSSR)

            })

            .catch(e => {

                handleError(e, instance, ErrorCodes.SETUP_FUNCTION)

            })

            // ...

        } else {

            // 反之

            handleSetupResult(instance, setupResult, isSSR)

        }

    } else {

        // 完成组件初始化

        finishComponentSetup(instance, isSSR)

    }

}

handleSetupResult


export function handleSetupResult(

    instance: ComponentInternalInstance,

    setupResult: unknown,

    isSSR: boolean

) {

    // setup 可能返回一个 render function

    if (isFunction(setupResult)) {

        // 如果是 node ssr,则返回一个内联渲染函数

        if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {

            instance.ssrRender = setupResult

        } else {

            instance.render = setupResult as InternalRenderFunction

        }

    } else if (isObject(setupResult)) {

        // 如果是 vnode

        if (__DEV__ && isVNode(setupResult)) {

        // setup 不应返回一个 vnode 应该是 render function

        warn(

            `setup() should not return VNodes directly - ` +

            `return a render function instead.`

        )

    }

    // 如果存在 devtools 就把 setup 返回结果赋值给 devtoolsRawSetupState

    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {

        instance.devtoolsRawSetupState = setupResult

    }

    // 这步会把 setup 的返回结果在进一步通过 proxy 进行包装

    instance.setupState = proxyRefs(setupResult)

    // ...

    } else if (__DEV__ && setupResult !== undefined) {

        warn(

            `setup() should return an object. Received: ${ setupResult === null ? 'null' :  typeof setupResult }`

        )

    }

    // 完成组件初始化

    finishComponentSetup(instance, isSSR)

}

finishComponentSetup


export function finishComponentSetup(

    instance: ComponentInternalInstance,

    isSSR: boolean,

    skipOptions?: boolean

) {

    // 既,业务代码,如:{setup: function(props, {emit}) { ... }, props: [...]}

    const Component = instance.type as ComponentOptions

    // ...

    if (!instance.render) {

        if (!isSSR && compile && !Component.render) {

            const template = (__COMPAT__ && instance.vnode.props && instance.vnode.props['inline-template']) || Component.template

            if (template) {

                // ...

                // instance.appContext 继承父为先,获取组件自己的为后

                const { isCustomElement, compilerOptions } = instance.appContext.config

                // Component = instance.type

                // 从这一步开始读取,业务代码中的 delimiters、compilerOptions 这两项编译配置

                const { delimiters, compilerOptions: componentCompilerOptions } = Component

                // 合并最终编译配置

                const finalCompilerOptions: CompilerOptions = extend(

                extend({ isCustomElement, delimiters }, compilerOptions), componentCompilerOptions)

                // ...

                // 将编译后的 render function 赋予 instance.type.render 上

                Component.render = compile(template, finalCompilerOptions)

                // ...

            }
        }

        // 将 render function 挂到 组件实例上(instance)

        // 如果没有 render function 使用 空函数(noop)代替

        instance.render = (Component.render || NOOP) as InternalRenderFunction

        // ...

    }

    if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {

        // 兼容 2.x ...

    }

    // ...

}

主要做了三件事:

  1. 初始化编译选项;

  2. 对未有 render function 的组件实例,挂载 render function;

  3. 对 2.x 做兼容。

改函数还涉及,vue3 的编译逻辑,这个后续继续分析

至此,mountComponent 中的初始化组件操作基本完成。

ps: render function


function render(_ctx, _cache) {

    with (_ctx) {

        const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

        return (_openBlock(), _createElementBlock("div", null, _toDisplayString(msg), 1 /* TEXT */))

    }

}