The internal implementation of the template’s build generate function was dissected in detail above, and how the render string was concatenated. This article will return to the template compilation of the upper method, parsing how to execute the render function to generate VNode objects, using the patch function to render VNode objects into DOM nodes

Execute the renderString to generate the render function

After analyzing generate function above, it was returned to baseCompile method. In fact, the return of this method was the return of generate function (object containing ast, code and other attributes). Return to the compile function that called the baseCompile method, which also directly returns the return value of the baseCompile function. Finally, we’ll go back to compileToFunction, which calls the compile method

// subsequent implementation of compileToFunction
const render = (__GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom)) as RenderFunction

// mark the function as runtime compiled; (renderas InternalRenderFunction)._rc = true

return (compileCache[key] = render)
Copy the code

Subsequent implementations pass the generated Render string as an argument to the new Funtion generator function, which then executes. Based on the final string above, the generated function executes to get the actual render method as follows:

function render(_ctx, _cache) {
 with (_ctx) {
  const { toDisplayString: _toDisplayString, createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
  return (_openBlock(), _createBlock(_Fragment, null, [ 
   _createTextVNode(_toDisplayString(message) + "".1 /* TEXT */),    
   _createVNode("button", { onClick: modifyMessage }, "Modify data".8 /* PROPS */["onClick"]]),64 /* STABLE_FRAGMENT */)}}Copy the code

Then set the _rc property of the render function to true, store the render function into compileCache with key as template string and value as render, and return the render function. Return to the finishComponentSetup method

// finishComponentSetup method template after compiling the subsequent implementation
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (instance.render._rc) {
  instance.withProxy = new Proxy(
    instance.ctx,
    RuntimeCompiledPublicInstanceProxyHandlers
  )
}
// support for 2.x options ...
Copy the code

Assign the returned render function to the Render property of the instance object. Render._rc true(assign _rc to true at the end of the method above), Proxy the instance. CTX object, and assign the object returned by the Proxy to the instance.withProxy property, Look at the hook of the Proxy agent object (RuntimeCompiledPublicInstanceProxyHandlers) internal implementation

export const RuntimeCompiledPublicInstanceProxyHandlers = extend({}, PublicInstanceProxyHandlers,
  {
    get(target: ComponentRenderContext, key: string) {
      // fast path for unscopables when using `with` block
    },
    has(_: ComponentRenderContext, key: string) {
      / /... has}})Copy the code

The main trap hook functions in this object are set to get and HAS (more on this later when the hook function is triggered using the withProxy property). With the completion of the finishComponentSetup function, Return to the mountComponent function (setupComponent -> setupStatefulComponent -> handleSetupResult -> finishComponentSetup) When the setupComponent function is complete, execute the setupRenderEffect function to parse the internal implementation of the method

Global dependent activeEffect(reactiveEffect)

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

Scheduler, allowRecurse ();}} This method executes the effect function, which is returned by componentEffect and createDevEffectOptions(instance). True, onTrack, onTrigger} four attributes of the object, the subsequent use of specific attributes will be detailed parsing), first look at the internal implementation of the effect method

// effect method definition
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

The effect method internally executes the createReactiveEffect function, again with fn and options

// createReactiveEffect method definition
function createReactiveEffect<T = any> (fn: () => T, options: ReactiveEffectOptions) :ReactiveEffect<T> {
  const effect = function reactiveEffect() :unknown {/ *... * /} 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

This function internally defines the reactiveEffect function and adds some new properties to the function (active=true, deps=[]). It then returns the function. The effect variable is equal to the value returned by the createReactiveEffect function (reactiveEffect). Start executing the Effect method (the reactiveEffect function). Take a look at the implementation inside the reactiveEffect function

const effectStack: ReactiveEffect[] = []

// Cleanup method definition
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect
  // createReactiveEffect set deps to []
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0}}let activeEffect: ReactiveEffect | undefined

// reactiveEffect is implemented internally
function reactiveEffect() :unknown {
    if(! effect.active) {// The createReactiveEffect method is executed with the active property set to true
      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]}}}let shouldTrack = true
const trackStack: boolean[] = []

// enableTracking method definition
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
Copy the code

ReactiveEffect Internally determines if an effect method already exists in the global effectStack array, and then calls the cleanup method to clean effect.deps (the array is empty when initialized). We then call the enableTracking method, which adds the global identity bit shouldTrack(initialized to true) to the global tracking array trackStack and sets shouldTrack to true. Regression into the reactiveEffect method, then store the effect method into the global effectStack array and assign the effect method to the global activeEffect variable. The fn method is then executed. Fn method is called createReactiveEffect function passed fn, is componentEffect function, look at the internal implementation of this function.

Execute the render function to generate the VNode object

// componentEffect function internal implementation
function componentEffect() {
    if(! instance.isMounted) {// The isMounted attribute is false
        let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode
        const { bm, m, parent } = instance
        // ...
        const subTree = (instance.subTree = renderComponentRoot(instance))
        // ...
    }
    else {/ *... * /}}Copy the code

This method internally executes the renderComponentRoot function, passing it as an instance object

export function renderComponentRoot(
  instance: ComponentInternalInstance
) :VNode {
    const {
    type: Component,
    vnode,
    proxy,
    withProxy,
    props,
    propsOptions: [propsOptions],
    slots,
    attrs,
    emit,
    render,
    renderCache,
    data,
    setupState,
    ctx
  } = instance
  let result
  currentRenderingInstance = instance
  // ...
  try {
      let fallthroughAttrs
      if (vnode.shapeFlag/ * * / 4 & ShapeFlags.STATEFUL_COMPONENT/ * * / 4) {
          constproxyToUse = withProxy || proxy result = normalizeVNode( render! .call( proxyToUse, proxyToUse! , renderCache, props, setupState, data, ctx ) ) fallthroughAttrs = attrs }else {} / /...
  } catch(err) {
      // ...}}Copy the code

RenderComponentRoot function inside first through render! .call(proxyToUse, …) Render (withProxy object) and renderCache(null array []); render (withProxy object); render (array []); render (array [])

With (_ctx){} with(_ctx){} Properties inside the with curly bracket do not need to specify a namespace and will automatically refer to the _ctx object. With (Proxy){key} triggers the Proxy’s has hook function (_ctx).

2, const {… } = _Vue structuring the _Vue object will first trigger the _ctx has hook function. Recalling the beginning of the Render String, const _Vue = Vue, that is, _Vue is the global Vue object. That deconstructed set of methods is the global Vue exposed method (toDisplayString, createVNode, createTextVNode, Fragment, openBlock, createBlock)

Execute the openBlock function (generate an array of child VNode objects)

3. Execute the render function return. The first is to execute the openBlock function (no arguments)

export const blockStack: (VNode[] | null=) [] []let currentBlock: VNode[] | null = null

// openBlock function definition
export function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null:)} [])Copy the code

The openBlock function internally assigns the global currentBlock variable an empty array [], and then pushes this variable to another global empty array blockStack, blockStack=[[]], which will be used to create vNodes

Parsing dynamic data (dependent on collection to the global targetMap object)

CreateBlock (); createBlock (); createBlock (); toDisplayString (); The argument is message. With (_ctx){message} triggers the has hook function

/ / get RuntimeCompiledPublicInstanceProxyHandlers objects, from the internal implementation of hooks
get(target: ComponentRenderContext, key: string) {
  // fast path for unscopables when using `with` block
  if ((key as any) === Symbol.unscopables) {
    return
  }
  returnPublicInstanceProxyHandlers.get! (target, key, target) },has(_: ComponentRenderContext, key: string) {
  const has = key[0]! = ='_' && !isGloballyWhitelisted(key)
  if(__DEV__ && ! has && PublicInstanceProxyHandlers.has! (_, key)) { warn(`Property The ${JSON.stringify(
        key
      )} should not start with _ which is a reserved prefix for Vue internals.`)}return has
}
Copy the code

Check that the property name key does not start with ‘_’ and is not a specific string, such as Object or Boolean (see the internal implementation of isGloballyWhitelisted). In this case, the key is message and has is true

After getting the value of the messgae _ctx. The message will trigger the get hook function, to determine whether the property name is equal to the first Symbol. Unscopables, at this time the key value of the message, so perform PublicInstanceProxyHandlers the get method. Watching the PublicInstanceProxyHandlers internal concrete implementation of the get method

/ / PublicInstanceProxyHandlers concrete implementation of the internal get hook function
get({ _: instance }: ComponentRenderContext, key: string) {
    const {
      ctx,
      setupState,
      data,
      props,
      accessCache,
      type,
      appContext
    } = instance
    // ...
    let normalizedProps
    if (key[0]! = ='$') {
        constn = accessCache! [key]if(n ! = =undefined) {}
        else if(setupState ! == EMPTY_OBJ && hasOwn(setupState, key)) { accessCache! [key] = AccessTypes.SETUP/ / 0
            return setupState[key]
        }
        // ...}}Copy the code

Target is the _ctx object. As mentioned earlier in this article, _CTx is the Proxy object for instance. CTX. In article 2, “Bidirectional data binding,” we mentioned the creation of instance objects and added the _ attribute value to instance. Return to the get hook function, check that the property name key(message) does not start with ‘$’ and does not exist in the accessCache object of instance, and check that the instance.setupState property is not empty. And message exists in the setupState object (mentioned in article 3, “Bidirectional data binding”). In this example, setupState is not an empty object and Message is an attribute of this object, so accessCache[message] = 0 returns the value of setupState[message].

Because the setupState object is a Proxy object that the setup function returns, the GET hook function is triggered when setupState[message] is executed

// unref method definition
export function unref<T> (ref: T) :T extends Ref<infer V>?V : T {
  return isRef(ref) ? (ref.value as any) : ref
}

// Implementation of the get hook function in shallowUnwrapHandlers
const shallowUnwrapHandlers: ProxyHandler<any> = {
    get: (target, key, receiver) = > unref(Reflect.get(target, key, receiver))
}
Copy the code

The value of setupstate. message is retrieved from the RefImpl instance object returned by calling ref. The unref method is then called to determine whether the __v_isRef attribute of the input parameter is true. In this case, message does, so return ref.value(message.value). Because Message is an instance object of RefImpl, the GET hook function is triggered when the property is fetched

// RefImpl class get hook function inside
get value() {
    track(toRaw(this), TrackOpTypes.GET/* "get" */.'value')
    return this._value
}

const targetMap = new WeakMap<any, KeyToDepMap>()
// track method definition
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if(! shouldTrack || activeEffect ===undefined) {
    return
  }
  let depsMap = targetMap.get(target)
  if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
  if(! dep) { depsMap.set(key, (dep =new Set()))}if(! dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep)if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}
Copy the code

Inside the hook function, the track function is first called to collect dependencies. Inside the function, the target attribute is first judged (the initial mount does not exist) in the targetMap(WeakMap object) global object. Set (target, (depsMap =new Map())), set key=target, value=new Map()(empty Map object), DepsMap (key, (dep = new set())); depsMap(dep = new set())); Set key=message, value=new Set()(empty Set object). Since DEP is an empty Set object, add the activeEffect global variable to deP. Then add the DEP object (Set object) to the DEPS array of the reactiveEffect method. The dependency is then executed to update the DOM when the set hook function is triggered after changing the value of message

CreateTextVNode Creates a text vNode object

Return to render function, get message=” test data “from a series of proxies (using this example as template parsing), start toDisplayString(‘ test data ‘)

export const toDisplayString = (val: unknown): string= > {
  return val == null
    ? ' '
    : isObject(val)
      ? JSON.stringify(val, replacer, 2)
      : String(val)
}
Copy the code

The toDisplayString function returns String(val) which is’ test data ‘

6. Execute createTextVNode with “test data” and 1

// createTextVNode function definition
export function createTextVNode(text: string = ' ', flag: number = 0) :VNode {
  return createVNode(Text, null, text, flag)
}
Copy the code

The createTextVNode function calls the createVNode method. The createTextVNode function calls the createVNode method using Symbol(‘Text’), NULL, test data, and 1. Mount is used when app.mount is initialized, and type is passed in when createApp is called. Now it is used to generate a text node. Take a look at the implementation inside createVNode

We first assign shapeFlag based on type type, because type is Symbol(‘Text’), so shapeFlag=0

Create a VNode object with patchFlag as 1

The normalizeChildren function is then called, which is primarily used to handle the children property of the node

export function normalizeChildren(vnode: VNode, children: unknown) {
  let type = 0
  const { shapeFlag } = vnode
  if (children == null) {}
  else if (isArray(children)) {}
  else if (typeof children === 'object') {}
  else if (isFunction(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 / / 8
    } 
  }
  vnode.children = children as VNodeNormalizedChildren
  vnode.shapeFlag |= type
}
Copy the code

Because this example Chinese child node of this node is a string and shapeFlag = 0, so the vnode. Children = ‘test data’, vnode. ShapeFlag = 0 | 8 = 8. Currentblock. push(vNode) to store the vNode in the global array currentBlock. The vNode object is then returned.

CreateVNode Creates the element VNode object

Return to render method, execute element node (button node, call createVNode directly) with “button”, {onClick: modifyMessage}, ‘modify data ‘, 8.

Since type is a string, shapeFlag is set to 1

Create the vNode object and assign the props property to {onClick: modifyMessage} and patchFlag to 8

Call normalizeChildren processing children attribute, the element node of children is also a string, so vnode. Children = ‘modify data, vnode. | 8 = 9 shapeFlag = 1. Finally, store the VNode object into the currentBlock array and return the VNode object

CreateBlock Creates the root VNode object

Return to render, createBlock (createTextVNode, createVNode) and createBlock (createBlock, createBlock)

export function createBlock(type: VNodeTypes | ClassComponent, props? : Record<string, any> |null, children? : any, patchFlag? : number, dynamicProps? : string[]) :VNode {
  const vnode = createVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    true /* isBlock: prevent a block from tracking itself */
  )
  // save current block children on the block vnode
  vnode.dynamicChildren = currentBlock || (EMPTY_ARR as any)
  // close block
  closeBlock()
  // a block is always going to be patched, so track it as a child of its
  // parent block
  if (shouldTrack > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}
Copy the code

CreateBlock creates a vNode by calling the createVNode method inside createBlock. The parameters are Symbol(‘Fragment’), null, 64, true

The createVNode method first assigns shapeFlag to 0 because type is Symbol. Create a VNode object and set patchFlag to 64

Perform normalizeChildren function, when handling children attribute, because children is an array, so the vnode. ShapeFlag = 0 | 16 = 16

Since isBlockNode=true is passed in, currentBlock.push(vNode) is not executed and the vNode object is returned.

Return to createBlock and assign the vNode. dynamicChildren property to the currentBlock array (the array contains the text vnode object and the element vnode object. Then execute closeBlock

export function closeBlock() {
  blockStack.pop()
  currentBlock = blockStack[blockStack.length - 1] | |null
}
Copy the code

This method removes the last item from the blockStack array. CurrentBlock = null; currentBlock = null; currentBlock = null; Finally, the createBlock method returns a vNode object of type Symbol(‘Fragment’). The children array contains two VNode objects, a Text of type Symbol(‘Text’) and an element of type ‘button’. At this point the parsing of the Render function is complete.

conclusion

This paper is mainly a detailed analysis of the render function execution process, first of all when parsing dynamic data message trigger get hook function, call track method for dependent collection (activeEffect variables collected into the global targetMap object); The createTextVNode method is then called to build the text vNode object; Call the createVNode method to build the VNode object of the button element. The createBlock method is finally called to build the root vNode object. The patch method will be parsed in detail to build real DOM elements using the generated VNode objects