Here is a Hello World code from the Vue3 documentation:

<div id="app">
  <p>{{ message }}</p>
</div>
Copy the code
const HelloVueApp = {
  data() {
    return {
      message: 'Hello Vue!! '
    }
  }
}

Vue.createApp(HelloVueApp).mount('#app')
Copy the code

It can render the string “Hello Vue” in the browser, which is not too easy for developers to do – add a text node to the HTML. But what steps does Vue as a framework go through to achieve this simple function?

As you can see from the above code, we need to call two vUe-related functions createApp and mount. These are the two main processes:

  1. Create ancreateApp
  2. Mount the applicationmount

Create an

const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)if (__DEV__) {
    injectNativeTagCheck(app)
  }

  const { mount } = app
  app.mount = (containerOrSelector: Element | string): any= > {
    const container = normalizeContainer(containerOrSelector)
    if(! container)return
    const component = app._component
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// clear content before mounting
    container.innerHTML = ' '
    const proxy = mount(container)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app'.' ')
    return proxy
  }

  return app
})
Copy the code

The createApp function does two things:

  1. Creating an App Instance
    • callensureRendererCreate the Renderer singleton
    • Call the RenderercreateAppMethod to create an App instance
  2. The agent of AppmountMethod, which handles the App container before calling the original method (i.emountThe input parameter to the function#app)

Create renderer

function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code

One can see that the ensureRenderer is a simple singleton implementation that guarantees that the Renderer will be created only once. RendererOptions, the parameter passed into createRenderer, is a set of instructions that consists of DOM manipulation and prop Patch. For example, the createText method provided by renderOptions calls the DOM’s document.createTextNode method to create the actual DOM node (we’ll use that later).

createRenderer

function createRenderer<
  HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
Copy the code

Is createRenderer a redundant function? No, there is a sibling function called createHydrationRenderer that creates the SSR Renderer. They both call the same baseCreateRenderer, but pass different parameters. The createHydrationRenderer also relies on the logic associated with hydration.

So using two functions here to divide and conquer two kinds of logic has two advantages:

  1. Tree-shaking, WEB environments are not packaged when packagedhydrationRelevant code.
  2. The code is easy to read and no arguments or comments are required to distinguish between creating two renderers.

baseCreateRenderer

function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
  
  // Alias directives in renderOptions with host prefix
  const {
    insert: hostInsert,
    ...
    createText: hostCreateText,
    ...
  } = options
  
  // Very important method, the VNode -> DOM
  const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
  ) = > {
    // patching & not same type, unmount old tree
    if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
      n1 = null
    }

    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        processText(n1, n2, container, anchor)
        break
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        } else if(shapeFlag & ShapeFlags.TELEPORT) { ; (typeas typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized,
            internals
          )
        } else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (typeas typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized,
            internals
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, ` (The ${typeof type}) `)}}// set ref
    if(ref ! =null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
  }
  
  // Related to this article, the method that handles text nodes (called in the patch method)
  const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) = > {
    if (n1 == null) {
      hostInsert(
        (n2.el = hostCreateText(n2.children as string)),
        container,
        anchor
      )
    } else {
      const el = (n2.el = n1.el!)
      if(n2.children ! == n1.children) { hostSetText(el, n2.childrenas string)
      }
    }
  }
  ...
  // Very important method, one of only three methods exposed by Renderer (one of which is SSR), calls patch and unmount internally
  const render: RootRenderFunction = (vnode, container) = > {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null.null.true)}}else {
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }  
}
Copy the code

This is the core function for creating the Renderer, and I can only show part of the code here. The main function of baseCreateRenderer is to create VNode handler methods that call the DOM API wrapped in renderOptions and implement processText in the code above. Two important entry methods are also derived (ignoring SSR) :

  • renderIs responsible for “rendering” a VNode into a real DOM, or for removing the DOM associated with a VNode, which is always called after a VNode has changed.
  • createAppMethod to create an App instance.

CreateApp, which we focused on, is a method returned by createAppAPI(Render, Hydrate).

createAppAPI

function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {... }}Copy the code

The createAppAPI function forms a closure that allows the internal createApp method to call the render method of the renderer.

Create an

function createApp(rootComponent, rootProps = null) {...const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null._context: context, ... mount(rootContainer: HostElement, isHydrate? : boolean): any { ... }})...return app
  }
Copy the code

EnsureRenderer ().createApp(… Args) is the createApp method returned by createAppAPI, so the HelloVueApp object passed in to the Demo code is assigned to the Component property of the App.

App is an oft-mentioned instance of Vue with the following properties and methods (implemented in the createApp method of Runtime-core/SRC/apicreateapp.ts) :

export interface App<HostElement = any> {
  version: string
  config: AppConfig use(plugin: Plugin, ... options:any[]) :this
  mixin(mixin: ComponentOptions): this
  component(name: string): Component | undefined
  component(name: string.component: Component): this
  directive(name: string): Directive | undefined
  directive(name: string.directive: Directive): this
  mount(
    rootContainer: HostElement | string, isHydrate? :boolean
  ): ComponentPublicInstance
  unmount(rootContainer: HostElement | string) :void
  provide<T>(key: InjectionKey<T> | string.value: T): this

  // internal, but we need to expose these for the server-renderer and devtools
  _uid: number
  _component: ConcreteComponent
  _props: Data | null
  _container: HostElement | null
  _context: AppContext
}
Copy the code

Notice that App has a mount method, which is very important and will be covered later.

Mount the application

  const { mount } = app
  app.mount = (containerOrSelector: Element | string) :any= > {
    const container = normalizeContainer(containerOrSelector)
    if(! container)return
    const component = app._component
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// clear content before mounting
    container.innerHTML = ' '
    const proxy = mount(container)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app'.' ')
    return proxy
  }
Copy the code

So if I go back to my original createApp method, it creates the App and then it’s going to proxy mount, and it’s going to take precedence in proxy mount and it’s going to call normalizeContainer and it’s going to handle containerOrSelector, #app in the Demo code, and then execute the original mount method.

Standardized application container

function normalizeContainer(container: Element | string): Element | null { if (isString(container)) { const res = document.querySelector(container) if (__DEV__ && ! res) { warn(`Failed to mount app: mount target selector returned null.`) } return res } return container }Copy the code

The normalizeContainer function simply replaces the string entry with the corresponding DOM object. The proxy mount method supports two parameter types:

  1. String (in Demo#appThe string is used to findidappThe DOM)
  2. DOM object

Let’s move on to the actual implementation of mount, as mentioned above, in the createApp method of the run-time core/ SRC/apicreateapp. ts file.

Mount the application

mount(rootContainer: HostElement, isHydrate? :boolean) :any {
  if(! isMounted) {const vnode = createVNode(
      rootComponent as ConcreteComponent,
      rootProps
    )
    ...
    if (isHydrate && hydrate) {
      hydrate(vnode as VNode<Node, Element>, rootContainer as any)}else {
      render(vnode, rootContainer)
    }
    isMounted = true. }},Copy the code

As you can see, the mount method has only two responsibilities:

  1. Create a VNodecreateVNode
  2. Calls the renderer’s render functionrender

Let’s look at creating a VNode…

createVNode

_createVNode

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
) :VNode {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: The ${type}. `)}type = Comment
  }

  if (isVNode(type)) {
    // createVNode receiving an existing vnode. This happens in cases like
    // <component :is="vnode"/>
    // #2078 make sure to merge refs during the clone instead of overwriting it
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }

  // class component normalization.
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

  // class & style normalization.
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    if (isProxy(props) || InternalObjectKey in props) {
      props = extend({}, props)
    }
    let { class: klass, style } = props
    if(klass && ! isString(klass)) { props.class = normalizeClass(klass) }if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if(isProxy(style) && ! isArray(style)) { style = extend({}, style) } props.style = normalizeStyle(style) } }// encode the vnode type information into a bitmap
  const shapeFlag = isString(type)? ShapeFlags.ELEMENT : __FEATURE_SUSPENSE__ && isSuspense(type)? ShapeFlags.SUSPENSE : isTeleport(type)? ShapeFlags.TELEPORT : isObject(type)? ShapeFlags.STATEFUL_COMPONENT : isFunction(type)? ShapeFlags.FUNCTIONAL_COMPONENT :0

  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    type = toRaw(type)
    warn(
      `Vue received a Component which was made a reactive object. This can ` +
        `lead to unnecessary performance overhead, and should be avoided by ` +
        `marking the component with \`markRaw\` or using \`shallowRef\` ` +
        `instead of \`ref\`.`.`\nComponent that was made reactive: `.type)}const vnode: VNode = {
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true.type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null.component: null.suspense: null.ssContent: null.ssFallback: null.dirs: null.transition: null.el: null.anchor: null.target: null.targetAnchor: null.staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null.appContext: null
  }

  // validate key
  if(__DEV__ && vnode.key ! == vnode.key) { warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
  }

  normalizeChildren(vnode, children)

  // normalize suspense children
  if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
    const { content, fallback } = normalizeSuspenseChildren(vnode)
    vnode.ssContent = content
    vnode.ssFallback = fallback
  }

  if (
    shouldTrack > 0 &&
    // avoid a block node from tracking itself! isBlockNode &&// has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.patchFlag ! == PatchFlags.HYDRATE_EVENTS ) { currentBlock.push(vnode) }return vnode
}
Copy the code

It should be clear from the diagram and code that createVNode creates a specific object to describe the node. It is worth noting that the shapeFlag attribute in VNode is related to the type of the child node, which usually indicates that the attribute will play an important role in the recursive process. In this case, the createApp argument passed in is an object with no child nodes, so the value of shapeFlag is shapeFlags.stateful_component, which represents the stateful component.

If you’re not familiar with VNode, check out these two articles:

  • Design VNode
  • Cognitive misunderstandings of Virtual DOM

normalizeChildren

function normalizeChildren(vnode: VNode, children: unknown) {
  let type = 0
  const { shapeFlag } = vnode
  if (children == null) {
    children = null
  } else if (isArray(children)) {
    type = ShapeFlags.ARRAY_CHILDREN
  } else if (typeof children === 'object') {
    if (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) {
      // Normalize slot to plain children for plain element and Teleport
      const slot = (children as any).default
      if (slot) {
        // _c marker is added by withCtx() indicating this is a compiled slot
        slot._c && setCompiledSlotRendering(1)
        normalizeChildren(vnode, slot())
        slot._c && setCompiledSlotRendering(-1)}return
    } else {
      type = ShapeFlags.SLOTS_CHILDREN
      const slotFlag = (children as RawSlots)._
      if(! slotFlag && ! (InternalObjectKeyinchildren!) ) {// if slots are not normalized, attach context instance
        // (compiled / normalized slots already have context); (childrenas RawSlots)._ctx = currentRenderingInstance
      } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
        // a child component receives forwarded slots from the parent.
        // its slot type is determined by its parent's slot type.
        if( currentRenderingInstance.vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS ) { ; (childrenas RawSlots)._ = SlotFlags.DYNAMIC
          vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
        } else {
          ;(children as RawSlots)._ = SlotFlags.STABLE
        }
      }
    }
  } else if (isFunction(children)) {
    children = { default: children, _ctx: currentRenderingInstance }
    type = ShapeFlags.SLOTS_CHILDREN
  } else {
    children = String(children)
    // force teleport children to array so it can be moved around
    if (shapeFlag & ShapeFlags.TELEPORT) {
      type = ShapeFlags.ARRAY_CHILDREN
      children = [createTextVNode(children as string)]}else {
      type = ShapeFlags.TEXT_CHILDREN
    }
  }
  vnode.children = children as VNodeNormalizedChildren
  vnode.shapeFlag |= type
}
Copy the code

This method determines the type of the child node and changes the shapeFlag value of the VNode. In this way, the type of the child node can be known according to the shapeFlag value during the operation of the VNode, facilitating the next step. In addition, in the function diagram of createVNode above, THE reason why THERE is a problem with Suspense component judgment is that it does not judge until shapeFlag is changed. I will confirm this later and add the rest information.

render

const render: RootRenderFunction = (vnode, container) = > {
  if (vnode == null) {
    if (container._vnode) {
      unmount(container._vnode, null.null.true)}}else {
    patch(container._vnode || null, vnode, container)
  }
  flushPostFlushCbs()
  container._vnode = vnode
}
Copy the code

As mentioned above, the function of rendering function is to associate VNode with DOM. It translates VNode into DOM through patch method, whose parameters are:

  1. The container’s VNode
  2. The VNode of the current node to render (HelloVueAppObject generated VNode)
  3. The container itself (id isappThe DOM)

patch

const patch: PatchFn = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false ) => { // patching & not same type, unmount old tree if (n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense, true) n1 = null } if (n2.patchFlag === PatchFlags.BAIL) { optimized = false n2.dynamicChildren = null } const { type, ref, shapeFlag } = n2 switch (type) { case Text: processText(n1, n2, container, anchor) break case Comment: processCommentNode(n1, n2, container, anchor) break case Static: if (n1 == null) { mountStaticNode(n2, container, anchor, isSVG) } else if (__DEV__) { patchStaticNode(n1, n2, container, isSVG) } break case Fragment: processFragment( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else ... }Copy the code

We already know that n2 in the patch parameter is the VNode generated by the HelloVueApp object, and its shapeFlags value is 4 (ststate component). ShapeFlags.COMPONENT has a value of 6 and contains stateful component 4 and function component 2, so it goes into the processComponent method.

Note: 4&6 === true

processComponent

const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { if (n1 == null) { if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctx as KeepAliveContext).activate( n2, container, anchor, isSVG, optimized ) } else { mountComponent( n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } } else { updateComponent(n1, n2, optimized) } }Copy the code

The processComponent function has only one distribution responsibility, which is to determine if the parent VNode of the current node exists, update updateComponent if it exists, and mount mountComponent if it does not.

mountComponent

  const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))
    ...
    setupComponent(instance)
    ...
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
    ...
  }
Copy the code

The mountComponent function does three things:

  1. Creating a component instancecreateComponentInstance
  2. Preparing component ContentsetupComponent
  3. Rendering componentsetupRenderEffect

createComponentInstance

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,
    ...
  }
  
  return instance
Copy the code

The createComponentInstance function creates and returns an object that is the component instance we accessed using this in the Options API.

setupComponent

function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children, shapeFlag } = instance.vnode
  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}
Copy the code
function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {...const { setup } = Component
  if (setup) {
    ...
  } else {
    finishComponentSetup(instance, isSSR)
  }
}
Copy the code

The setupComponent function handles component configuration information in two steps:

  1. Initialize thepropsslots
  2. callsetupStatefulComponentHandles other configuration information for components

The setupStatefulComponent function handles the rest of the component’s configuration information (methods/data/Watch, etc.) by calling the finishComponentSetup method. Prior to this, the Composition API is also distinguished from the Options API by the presence or absence of setup, which requires the setup function to be executed first.

function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {...if(compile && Component.template && ! Component.render) {if (__DEV__) {
      startMeasure(instance, `compile`)
    }
    Component.render = compile(Component.template, {
      isCustomElement: instance.appContext.config.isCustomElement,
      delimiters: Component.delimiters
    })
    if (__DEV__) {
      endMeasure(instance, `compile`)}}...// support for 2.x options
  if (__FEATURE_OPTIONS_API__) {
    currentInstance = instance
    applyOptions(instance, Component)
    currentInstance = null}... }Copy the code

The responsibilities of the finishComponentSetup function are also clear, two things:

  1. If there iscompileFunction and the template is not compiled
  2. callapplyOptionsProcess component configuration information

The Vue3compileAlthough the process of Vue2 is clearer than that of Vue2, but it also needs a longer length to explain, so I leave it to other articles. Here is only a compiled process diagram for easy understandingcompileInput and output of functions and core flow:

The applyOptions function handles a number of options, each of which involves a bit of a waste of time. Since we have only configured the data option, we will only analyze the flow of data processing.

export function applyOptions(
  instance: ComponentInternalInstance,
  options: ComponentOptions,
  deferredData: DataFn[] = [],
  deferredWatch: ComponentWatchOptions[] = [],
  deferredProvide: (Data | Function)[] = [],
  asMixin: boolean = false
) {
  const { data: dataOptions, ... } = options
  ...
  isInBeforeCreate = true
  callSyncHook(
    'beforeCreate',
    LifecycleHooks.BEFORE_CREATE,
    options,
    instance,
    globalMixins
  )
  isInBeforeCreate = false.if (dataOptions) {
    resolveData(instance, dataOptions, publicThis)
  }
  ...
  callSyncHook(
    'created',
    LifecycleHooks.CREATED,
    options,
    instance,
    globalMixins
  )
  ...
}
Copy the code

You can see that applyOptions does the following:

  1. callbeforeCreateThe props and slots that were handled earlier can only be fetched by instance because the configuration item has not yet been processed
  2. Distribute handlers, like the one hereresolveData
  3. callcreatedHook to retrieve all configuration items
  4. Inject other lifecycle hooks into the corresponding properties of the instance, for exampleinstance.bm.push(beforeMount)(No code shown)

I’ll store it in an arraycreatedLater lifecycle hooks, because mixin and extends’s parent classes may also have these hook functions configured

function resolveData(instance: ComponentInternalInstance, dataFn: DataFn, publicThis: ComponentPublicInstance) {
  const data = dataFn.call(publicThis, publicThis)
  if(! isObject(data)) { __DEV__ && warn(`data() should return an object.`)}else if (instance.data === EMPTY_OBJ) {
    instance.data = reactive(data)
  } else {
    // existing data: this is a mixin or extends.
    extend(instance.data, data)
  }
}
Copy the code

The dataFn is the data option configured by the user. ResolveData will call the dataFn function to get the returned object (if the dataFn is not a function, it will send an alarm and no code is attached), and then determine whether the data source is mixin/extends. If yes, reactive objects are merged directly into the data property of the component instance. If not, reactive objects are called to become reactive objects and then merged.

Componentoptions.ts = componentOptions.ts = componentOptions.ts = componentOptions.ts

setupRenderEffect

const setupRenderEffect: SetupRenderEffectFn = ( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) => { // create reactive effect for rendering instance.update = effect(function componentEffect() {... }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions) }Copy the code

The setupRenderEffect function internally provides an update method to the component instance, which is returned by the effect function.

function isEffect(fn: any) :fn is ReactiveEffect {
  return fn && fn._isEffect === true
}

export function effect<T = any> (fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ) :ReactiveEffect<T> {
  if (isEffect(fn)) {
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  if(! options.lazy) { effect() }return effect
}
Copy the code

Function effect

  1. Determine the referencefn(that is, passed in when calledcomponentEffect) whether has beeneffectIf so, return the function before processingfn.raw
  2. callcreateReactiveEffectFunction gets the proxy function, which is called internallycomponentEffectTake some notes before
    • trackStackRecord a Boolean value. What is it
    • effectStackRecord in executioneffectFunction stack, render complete then out of the stack
    • activeEffectRecord the latest executioneffectfunction
  3. Executing proxy functionseffect

Therefore, the update function of the component instance is actually the proxy function effect returned by createReactiveEffect.

function createReactiveEffect<T = any> (fn: () => T, options: ReactiveEffectOptions) :ReactiveEffect<T> {
  const effect = function reactiveEffect() :unknown {
    if(! effect.active) {return options.scheduler ? undefined : fn()
    }
    if(! effectStack.includes(effect)) { cleanup(effect)try {
        enableTracking()
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]}}}asReactiveEffect effect.id = uid++ effect.allowRecurse = !! options.allowRecurse effect._isEffect =true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}
Copy the code

As you can see, the proxy function returned by createReactiveEffect records some information and adds some attributes to it:

  • idOn the logo
  • allowRecurseWhether or not recursion is allowed
  • _isEffectIs it handled by the side effect function (proxy)
  • activeWhether to stop the side effect of the function
  • rawThe original function
  • depsCollect additional side effects functions tracked by the current component
  • optionsSide effect option

Next we look at the original function componentEffect executed by fn(), which is also the actual mount and update function in effect.

function componentEffect() {
  if(! instance.isMounted) { ...const { bm, m, parent } = instance
    // beforeMount hook
    if (bm) {
      invokeArrayFns(bm)
    }
    ...
    // Get the component VNode
    const subTree = (instance.subTree = renderComponentRoot(instance))
    ...
    // patch
    patch(
      null,
      subTree,
      container,
      anchor,
      instance,
      parentSuspense,
      isSVG
    )
    ...
    // mounted hook
    if (m) {
      queuePostRenderEffect(m, parentSuspense)
    }
    ...
    instance.isMounted = true
  } else {
    // Update logic. }Copy the code

The function componentEffect mounts the following logic:

  1. callbeforeMountThe hook
  2. callrenderComponentRootGet component VNode (where component will be calledrenderFunction)
  3. callpatchfunction
  4. callmounthook

RenderComponentRoot provides the following vNodes for the current component:

{
  children: "Hello Vue!!".patchFlag: 0.shapeFlag: 8.type: Symbol(Text),
  __v_isVNode: true. }Copy the code

Then we continue to look at the patch function:

.switch (type) {
  case Text:
    processText(n1, n2, container, anchor)
...
Copy the code

Since VNode’s type attribute is Symbol(Text), the Text node is processed by calling the original processText function.

const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) = > {
  if (n1 == null) {
    hostInsert(
      (n2.el = hostCreateText(n2.children as string)),
      container,
      anchor
    )
  } else {
    const el = (n2.el = n1.el!)
    if(n2.children ! == n1.children) { hostSetText(el, n2.childrenas string)}}}Copy the code

Document. insertBefore (processText); hostCreateText (processText);

const doc = (typeof document! = ='undefined' ? document : null) as Document
...
export {
  insert: (child, parent, anchor) = > {
    parent.insertBefore(child, anchor || null)},...createText: text= > doc.createTextNode(text),
}
Copy the code

At this point the Hello Vue!!!!! It is rendered to HTML, displayed on the page, and then calls the Mounted lifecycle hook (Suspense component, of course).

conclusion

For Vue3, rendering the Hello World component goes through several main stages:

  1. Create renderercreateRenderer
  2. Create ancreateApp
  3. Creating a Virtual Node_createVNode
  4. Perform renderingrender

The patch process we often refer to in Vue2 is in the rendering stage of Vue3.

Render distribution functionpatchIt does not deal with the actual logic, but just allocates the corresponding handler function for each node type, which is internally recursive because of the node tree.