Vue3组件初始化流程(二): mountComponent [Vue3源码系列_xiaolu]

19 篇文章 9 订阅

分析mopuntConponent的三个方法

前一章我们分析到了mountCompoent方法,其内部调用了三个方法:

  1. createComponentInstance
  2. setupCompoent
  3. setupRenderEffect

现在来看看这三个方法

createComponentInstance

createComponentInstance方法 路径: core\packages\runtime-core\src\component.ts

export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const type = vnode.type as ConcreteComponent
  // inherit parent app context - or - if root, adopt from root vnode
  const appContext =
    (parent ? parent.appContext : vnode.appContext) || emptyAppContext

  const instance: ComponentInternalInstance = {
    uid: uid++,
    vnode,
    type,
    parent,
    appContext,
    root: null!, // to be immediately set
    next: null,
    subTree: null!, // will be set synchronously right after creation
    effect: null!,
    update: null!, // will be set synchronously right after creation
    scope: new EffectScope(true /* detached */),
    render: null,
    proxy: null,
    exposed: null,
    exposeProxy: null,
    withProxy: 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!, // to be set immediately
    emitted: null,
    // props default value
    propsDefaults: EMPTY_OBJ,
    // inheritAttrs
    inheritAttrs: type.inheritAttrs,
    // 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,
    sp: null
  }
  if (__DEV__) {
    instance.ctx = createDevRenderContext(instance)
  } else {
    instance.ctx = { _: instance }
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)

  // apply custom element special handling
  if (vnode.ce) {
    vnode.ce(instance)
  }
  
  return instance
}

createComponentInstance方法做了什么?

  1. 创建一个instance对象,上面有很多属性
  2. 返回instance对象

很明显能看出,这个方法就是创建一个instance实例,实例上有特别多的属性,最后返回了这个instance实例,这就是组件实例

但这里只是创建,所有属性都还没有值,之后会调用setupComponent方法去初始化组件实例

因此来看setupComponent方法的实现

这一步的注释: 创建组件实例

setupComponent

setupComponent方法 路径: core\packages\runtime-core\src\component.ts

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR
  
  const { props, children } = instance.vnode
  const isStateful = isStatefulComponent(instance)
  //todo To: initProps
  initProps(instance, props, isStateful, isSSR)
  //todo To: initSlots
  initSlots(instance, children)

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

setupComponent方法做了什么?

  1. 解构出新vnode的props和children属性
  2. 通过isStatefulComponent(instance)判断是否是有状态的组件(没有状态的组件是函数式组件)
  3. initProps初始化props,attrs(todo)
  4. initSlots初始化slots(todo)
  5. 根据isStateful变量决定是否执行setupStatefulComponent,因为组件初始化的时候,我们的组件不是函数式组件,肯定是有状态的,因此会执行setupStatefulComponent方法
  6. 返回setupResult

因为setupResult是setupStatefulComponent方法执行后的返回值,所以要看setupStatefulComponent方法实现

这一步的注释在:setupComponent

setupStatefulComponent

setupStatefulComponent方法 路径: core\packages\runtime-core\src\component.ts

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions
  
  // 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
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))

  // 2. call setup()
  const { setup } = Component
  if (setup) {
    //todo To: createSetupContext
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    setCurrentInstance(instance)
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()

    if (isPromise(setupResult)) {
    
    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

setupStatefulComponent方法做了什么?

  1. 解构出setup方法
  2. 判断setup是否存在
  3. 如果setup存在,根据setup参数的多少决定是否初始化instance.setupContext对象,中间调用了createSetupContext方法(todo)
  4. 调用setup方法,并把返回值给setupResult(这就是用户写的setup方法的返回值)
  5. 调用handleSetupResult方法对setupResult进行处理
  6. 如果Setup不存在,执行finishComponentSetup方法

这里主要就是调用了用户写的setup方法,将返回值赋值给setupResult

接着执行handleSetupResult方法处理setupResult,因此来看handleSetupResult方法的实现

这一步的注释在:setupStatefulComponent

handleSetupResult

handleSetupResult方法 路径: core\packages\runtime-core\src\component.ts

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  if (isFunction(setupResult)) {
    // setup returned an inline render function
    if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
        // 忽略
    } else {
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) {
    // setup returned bindings.
    // assuming a render function compiled from template is present.
    instance.setupState = proxyRefs(setupResult)
   
  } else if (__DEV__ && setupResult !== undefined) {
   // 忽略
  }
  finishComponentSetup(instance, isSSR)
}

handleSetupResult方法做了什么?

  1. 判断setupResult是否是函数,如果是函数,那就是用户自己写的render函数了,instance.render = setupResult
  2. 判断setupResult是否是对象,如果是对象,就把这个对象赋值给instance.setupState
  3. 最后执行finishComponentSetup方法

根据setupResult的类型执行相对应的操作,如果是函数,代表的是用户自己编写的render函数,如果是对象,代表的是用户setup函数想要返回的对象

接着执行finishComponentSetup方法,接下来看finishComponentSetup方法的实现

这一步的注释在: handleSetupResult

finishComponentSetup

finishComponentSetup方法 路径: core\packages\runtime-core\src\component.ts

export function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean,
  skipOptions?: boolean
) {
  const Component = instance.type as ComponentOptions

  // template / render function normalization
  // could be already set when returned from setup()
  if (!instance.render) {
    // only do on-the-fly compile if not in SSR - SSR on-the-fly compilation
    // is done by server-renderer
    if (!isSSR && compile && !Component.render) {
      const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
        Component.template
      if (template) {
        const { isCustomElement, compilerOptions } = instance.appContext.config
        const { delimiters, compilerOptions: componentCompilerOptions } =
          Component
        const finalCompilerOptions: CompilerOptions = extend(
          extend(
            {
              isCustomElement,
              delimiters
            },
            compilerOptions
          ),
          componentCompilerOptions
        )
        //todo To: compile
        Component.render = compile(template, finalCompilerOptions)
      }
    }
    instance.render = (Component.render || NOOP) as InternalRenderFunction
  }

  // support for 2.x options
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
    setCurrentInstance(instance)
    pauseTracking()
    //todo To: applyOptions
    applyOptions(instance)
    resetTracking()
    unsetCurrentInstance()
  }
}

finishComponentSetup方法做了什么?

  1. 判断instance是否有render函数
  2. 如果没有render函数,经过一系列处理之后,通过compile方法(涉及编译todo)获得render函数赋值给instance.render
  3. applyOptions方法对vue2.0做一些兼容处理

判断当前实例是否有render函数(前一步在用户没有编写render函数时,此时render是不存在的),如果没有render函数,通过compile方法获得render函数,最后通过applyOptions对Vue2.0做兼容处理

这一步的注释在: finishComponentSetup

setupComponent执行完,做了什么?

到了这里,setupComponent的调用栈执行完毕,因此让我们一一返回

  1. finishComponentSetup: 生成render函数以及对vue2.0做兼容处理
  2. handleSetupResult: render,setupState以及对vue2.0做兼容处理返回到setupStatefulComponent
  3. setupStatefulComponent: 将render,setupState以及对vue2.0做兼容处理返回到setupComponent
  4. setupComponent: 将props,attrs,slots,render, setupstate,setupContext以及对vue2做了兼容处理返回到mountComponent
  5. 因此mountCompoent内部执行完setupComponent后: instance上多出了props,attrs,slots,render,setupState,setupContext(取决于setup的参数多少),以及对vue2做了兼容处理

这一步的注释在: 调用栈回到mountComponent

接下来回到mountComponent,继续执行setupRenderEffect方法,来看其实现

setupRenderEffect

setupRenderEffect方法 路径: core\packages\runtime-core\src\renderer.ts

const setupRenderEffect: SetupRenderEffectFn = (
  instance,
  initialVNode,
  container,
  anchor,
  parentSuspense,
  isSVG,
  optimized
) => {
    const componentUpdateFn = () => {}
    // create reactive effect for rendering
    //todo To: new ReactiveEffect & queueJob
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(instance.update),
      instance.scope // track it in component's effect scope
    ))
    //todo To: effect
    const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
    update.id = instance.uid
    // allowRecurse
    // #1801, #2043 component render effects should allow recursive updates
    //todo To: toggleRecurse
    toggleRecurse(instance, true)
    if (__DEV__) {
    }
    update()
}    

setupRenderEffect方法做了什么?

  1. 创建更新函数 componentUpdateFn
  2. 创建更新机制 effect,new ReactiveEffect
  3. 更新视图,首次渲染 update()

这一步的注释在setupRenderEffect

现在来看 componentUpdateFn方法的实现

componentUpdateFn

componentUpdateFn方法 路径: core\packages\runtime-core\src\renderer.ts

const componentUpdateFn = () => {
      // 判断mounted
      if (!instance.isMounted) {
        // 如果mounted为false
        let vnodeHook: VNodeHook | null | undefined
        // 获取el和props
        const { el, props } = initialVNode
        // 获取beforeMounted mounted钩子和parent
        const { bm, m, parent } = instance
        const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
        //todo  To: toggleRecurse
        toggleRecurse(instance, false)
        // beforeMount hook
        if (bm) {
          invokeArrayFns(bm)
        }
        // onVnodeBeforeMount
        if (
          !isAsyncWrapperVNode &&
          (vnodeHook = props && props.onVnodeBeforeMount)
        ) {
          invokeVNodeHook(vnodeHook, parent, initialVNode)
        }
        if (
          __COMPAT__ &&
          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
        ) {
          instance.emit('hook:beforeMount')
        }
        toggleRecurse(instance, true)

        if (el && hydrateNode) {

        } else {
          // 执行renderComponentRoot,转为vnode
          const subTree = (instance.subTree = renderComponentRoot(instance))
          // 执行patch
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          initialVNode.el = subTree.el
        }
        // mounted hook
        if (m) {
          queuePostRenderEffect(m, parentSuspense)
        }
        // onVnodeMounted
        if (
          !isAsyncWrapperVNode &&
          (vnodeHook = props && props.onVnodeMounted)
        ) {
          const scopedInitialVNode = initialVNode
          queuePostRenderEffect(
            () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
            parentSuspense
          )
        }
        // isMounted置为true,之后进来就走unpate分支了
        instance.isMounted = true

        // #2458: deference mount-only object parameters to prevent memleaks
        initialVNode = container = anchor = null as any
      } 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: VNodeHook | null | undefined

        // Disallow component effect recursion during pre-lifecycle hooks.
        toggleRecurse(instance, false)
        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)
        }
        toggleRecurse(instance, true)

        // render
        // 执行renderComponentRoot,转化为vnode
        const nextTree = renderComponentRoot(instance)
        const prevTree = instance.subTree
        instance.subTree = nextTree

        // 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
        )

        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
          )
        }
      }
    }

componentUpdateFn方法做了什么?

  1. 判断isMounted变量
  2. 如果isMounted为false,代表没有挂载,执行renderComponentRoot方法,转化为vnode赋值给subtree,然后执行patch(null, subtree)方法进行挂载
  3. 如果isMounted为true,代表已经挂载,执行renderComponentRoot方法,转化为vnode赋值给subtree,然后patch(oldsubtree, subtree)方法进行更新

因此这个更新函数就是将组件转为vnode,并执行patch方法执行相应的挂载更新操作

注意: 这里调用patch,是递归调用哦

因为我们是通过patch方法进入到这里的,这里又再次调用了patch,因此这是明显的递归调用

那么这次调用patch,是不是就又要根据类型判断执行相对应的一系列操作,之后又会走到这里执行patch方法?

这是不是形成了一个圈,在组件树经过patch执行process操作之后,来到这里继续递归patch子节点,直到整个节点树被递归完毕?

好!先在这里留下猜测:递归的patch

更新机制

创建更新机制 路径: core\packages\runtime-core\src\renderer.ts

const effect = (instance.effect = new ReactiveEffect(
  componentUpdateFn,
  () => queueJob(instance.update),
  instance.scope // track it in component's effect scope
))

这一步做了什么?

这个涉及到响应式和渲染任务队列,因此我只在这先说作用,后续会细说(组件更新流程)

创建effect,当组件更新时(触发依赖更新),会去调用我们之前创建的更新函数componentUpdateFn

componentUpdateFn更新函数内部会去执行renderComponentRoot方法,然后执行patch方法递归更新

首次更新视图

update方法 路径: core\packages\runtime-core\src\renderer.ts

const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)

// 首次更新视图
update()

update方法做了什么?

这里的update方法其实是effect.run方法,这里也是涉及到响应式的effect类的run方法,所以我先说功能

其实这个run方法就是调用我们的更新函数,所以这里的update的执行也就是执行componentUpdateFn

componentUpdateFn更新函数内部会去执行renderComponentRoot方法,然后执行patch方法递归,因为初始化组件,n1都为null,所以会执行递归挂载操作

在update执行完之后,视图渲染完毕,用户就能看到了

组件初始化完成

到此,组件初次挂载也就是初始化完成

总结

这一章做了什么?

  1. createComponentInstance方法创建组件实例instance
  2. setupComponent方法初始化组件实例
  3. setupRendererEffect方法创建组件更新函数,创建更新机制,首次视图更新

这一章的todo如图

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值