“This is my 37th day of participating in the First Challenge 2022. For more details: First Challenge 2022.”

Previously on & Background

This essay discusses the corresponding rendering functions of two other commonly used functions: custom components and dynamic components. Both are processed into _C (componentName, data, children) call form;

At this point in the compilation phase of the root instance, only when the compilation phase of the root instance is finished, the rendering function of the root instance is available, and then the rendering function of the root instance is created by Watcher.

This essay is prepared to take a look at our entire template after compiling the render function body, then we look at the render function generation;

The rendering function of the root instance

What we’ve been talking about here is the render function of the root instance, as opposed to the render function of the child instance;

2.1 test. HTML templates

<div id="app">
   <div class="staticR">
      <article>hahahah</article>
   </div>
   <input :type="inputType" v-model="inputValue" />
   <span v-for="item in someArr" :key="index">{{item}}</span>
   <div v-if="inputType === 'checkbox'">inputType=checkBox</div>
   <div v-else-if="inputType === 'radio'">inputType=radio</div>
   <div v-else>inputType=something-else</div>
    {{ msg }}
   <some-com :some-key="forProp"></some-com>
   <div>someComputed = {{someComputed}}</div>
   <component is="someCom">
     <div>The component of the slot</div>
   </component>
 <div class="static-div">Static node</div>
</div>
Copy the code

2.2 The body of the render function of the root instance

The above 2.1 template will eventually be compiled into the render function body below. Note that it is not technically the render function, but the body, the actual render function, needs to be wrapped with the with(this) statement;

"_c('div',{attrs:{\"id\":\"app\"}},[_m(0),_v(\" \"),((inputType)==='checkbox')?_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(inputValue),expression :\"inputValue\"}],attrs:{\"type\":\"checkbox\"},domProps:{\"checked\":Array.isArray(inputValue)?_i(inputValue,null)>-1:( inputValue)},on:{\"change\":function($event){var $$a=inputValue,$$el=$event.target,$$c=$$el.checked?(true):(false); if(Array.isArray($$a)){var $$v=null,$$i=_i($$a,$$v); if($$el.checked){$$i<0&&(inputValue=$$a.concat([$$v]))}else{$$i>-1&&(inputValue=$$a.slice(0,$$i).concat($$a.slice($$i+1) ))}}else{inputValue=$$c}}}}):((inputType)==='radio')?_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:( inputValue),expression:\"inputValue\"}],attrs:{\"type\":\"radio\"},domProps:{\"checked\":_q(inputValue,null)},on:{\"chan ge\":function($event){inputValue=null}}}):_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(inputValue) ,expression:\"inputValue\"}],attrs:{\"type\":inputType},domProps:{\"value\":(inputValue)},on:{\"input\":function($event) {if($event.target.composing)return; inputValue=$event.target.value}}}),_v(\" \"),_l((someArr),function(item){return _c('span',{key:index},[_v(_s(item))])}),_v(\" \"),(inputType === 'checkbox')?_c('div',[_v(\"inputType=checkBox\")]):(inputType === 'radio')?_c('div',[_v(\"inputType=radio\")]):_c('div',[_v(\"inputType=something-else\")]),_v(\"\\n\\t\"+_s(msg)+\"\\n\\t \"),_c('some-com',{attrs:{\"some-key\":forProp}}),_v(\" \"),_c('div',[_v(\"someComputed = \"+_s(someComputed))]),_v(\" \ "), _c (\ "someCom \", {tag: \ "component \}", [_c (' div ', [_v (\ "component slot \")])]), _v (\" \ "), _c (' div '{staticClass: \ "static - div \}", [_v (\ \ "" static node)])], 2)"
Copy the code

This big mess is really confusing, so I formatted it manually. This is a manual job 😂😂, which is more painful than code words;

A quick reminder of what the _c method does: it is used to create elements, but the process can be better understood by looking at its arguments:

  1. The first parameter is the tag name, which can be a custom component name, a dynamic component name, or a tag nameHTMLNative tag;
  2. The inline property of the first argument,attrs
  3. The third argument is an array that identifies the children of the current element;
let renderFunctionsString = '_c(// render div#app 'div', {attrs: {"id": "App"}}, / / div # app inline attribute [/ / the array is div # _m app child element (0), _v (" "), (((inputType)==='checkbox') // Handle  -1 : (inputValue) }, on: { "change": Function ($event) {// If type is checkbox, Var $$a = inputValue, $$el = $event. Target, $$c = $$el.checked? (true) : (false); the if (Array. IsArray ($$) a) {/ / processing checbox binding Array item / / the value of the binding update to the Array of the var $$v = null, $$I = _i ($$a, $$v); / / _i indexOf if ($$el. Checked) {$$I < 0 && (inputValue = $$a.c oncat ([$$v]))} else {$$I > 1 && (inputValue = $$a.s lice (0, $$I) concat ($$a.s lice ($$I + 1)))}} else {/ / array inputValue = $$, c}}}}) : ((inputType) === 'radio') // Input Type is radio? _C (' INPUT ', {directives: [{name: "model", rawName: "v-model", value: (inputValue), expression: "inputValue" }], attrs: { "type": "radio" }, domProps: { "checked": _q(inputValue, null) }, on: { "change": function($event) { inputValue = null } } } ) : _c( 'input', { directives:[{ name: "model", rawName: "v-model", value: (inputValue), expression: "inputValue" }], attrs: { "type": inputType }, domProps:{ "value": (inputValue)}, on:{ "input": function ($event) { if ($event.target.composing) return;  inputValue = $event.target.value } } } ), _v(" "), _l(// handle {{item}} (someArr), function (item) {return _c('span', { key:index }, [ _v(_s(item)) ] ) } ), _v(" "), (inputType === 'checkbox') // 
      
? _c( 'div', [ _v("inputType=checkBox") ] ) : (inputType === 'radio') //
? _c( 'div', [ _v("inputType=radio") ] ) : _c( //
'div', [ _v("inputType=something-else") ] ), _v(\n\t"+_s(msg)+"\n\t"), Rendering text / / {{MSG}} _c (/ / rendering < some com - > < / some com - > components' some - com '{attrs: {" some - key ": ForProp}}), _v (" "), / / empty line _c (/ / render the < div > someComputed = {{someComputed}} < / div > 'div', [_v (" someComputed = "+ _s (someComputed)]), / / _v (" "), _c (/ / rendering dynamic component < component is =" someCom ">..." someCom ", {tag: "Component"}, [_c (/ / dynamic component component labels' div 'child element of the child element, [_v (" component slot ")])]), _v (" "), _c (' div', / / rendering < div class = "static - div > static node < / div > {staticClass: static -" div "}, [_v (" static node ")]], 2) `
Copy the code

There is a lack of a comparison view here. It is recommended to compare the template code in test. HTML with the body of the render function.

I guarantee that this is the network you distance from the Vue rendering function recently, the original intention of writing this column is to let everyone can read Vue source code, refuelling old iron people;

3. Rendering functions

Previously we also referred to the rendering function as in the second topic above. The main body of the rendering function is a loose process, which we also mentioned earlier. This topic is to talk about the actual rendering function generation process;

3.1 the parcel with the (this)

We know a lot about generate, the call stack is very deep, and the process of converting the AST obtained in the Parse phase into the body of the render function is now complete. This process is an intermediate process of generate, and the subsequent process comes from the second half of the generate method, coded as follows

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
) :CodegenResult {
  const state = new CodegenState(options)

  // Generate string format code such as '_c(tag, data, children, normalizationType)'
  const code = ast 
     ? (ast.tag === 'script' 
         ? 'null' 
         : genElement(ast, state))
     : '_c("div")'
  
  // This is the next step to generating the render function body
  // Wrap the with statement around the body of the render function
  // Upgrade the render function of the static root node
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
}
Copy the code

Render (div, {id: “app”},…); render (div, {id: “app”},…) The string wrapped after the with(this) statement;

By the way, why wrap the with(this) statement? The with(this) statement extends the js scope chain, using the variable in the with(obj) block to find the property in obj as the value of the variable.

SomeArr = someArr; someArr = someArr; someArr = someArr;

So where does this.somearr come from?

Remember earlier when we talked about responsive data, data, computed, and props all ended up propping to the VM? Thus, data, computed, and props are all propped to Vue instances, which greatly simplifies the rendering function processing of Vue.

Imagine if there is no Vue instance to delegate to, then we write templates with data.somearr, computed. SomeArr, props. SomeArr.

3.2 Generating the rendering function

The return from 3.1 above {render, staticRenderFns} returns comile method calls off the stack and back to the following compileToFunctions scope:

export function createCompileToFunctionFn (compile: Function): Function { const cache = Object.create(null) return function compileToFunctions ( template: string, options? : CompilerOptions, vm? : Component ): CompiledFunctionResult {// compileToFunctions scope // compile // CompiledFunctionResult {// compileToFunctions scope // compile // CompiledFunctionResult {render, renderStaticFns} const compiled = compile(template, options) const res = {} Form.render = createFunction(compiled. Render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, Return (cache[key] = res)}}Copy the code

3.3 createFunction method

SRC /compiler/to-function.js -> function createFunction

Method parameters:

  1. code: code string
  2. errorsCollection:

The new Function creates a Function by passing in a string

function createFunction (code, errors) {
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err, code })
    return noop
  }
}
Copy the code

3.4 Render function forming call stack successively out of stack

So after this step, compileToFunctions will fetch the final result like this:

const res = { render: function () { return with (this) { return _c('div', { id: 'app' }, [.....] ) } }, staticRenderFns: [....] }Copy the code

The scope of compileToFunctions will also go out of the stack after caching is added, back to createCompiler, returning the result as the value of the compileToFunctions property.

export function createCompilerCreator (baseCompile) {
  return function createCompiler () {
 
    function compile () {
      
      const compiled = baseCompile(template.trim(), finalOptions)
   
      return compiled
    }

    return {
      compile,
      // createCompileToFunctionFn(compile)->
      // -> { render, staticRenderFns }
      compileToFunctions: createCompileToFunctionFn(compile) 
    }
  }
}
Copy the code

The createCompiler scope returns {compile, compileToFunctions} and exited to where?

Proptotype.$mount = Vue. Proptotype

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
      // ...
      // Compile the template to get the dynamic rendering function and static rendering function
      // This object is the result of the above processing
      const { 
          render, 
          staticRenderFns 
      } = compileToFunctions(template,...)

      // Put two rendering functions on vm.$options,
      $options for the vue.prototype. _render method
      options.render = render // funciton () { return with(this) {...} }
      options.staticRenderFns = staticRenderFns
    }
  }
  // Perform the mount
  return mount.call(this, el, hydrating)
}
Copy the code

Four,

The body of the render function is a call to _c(), whose first argument is the name of the tag, the second is the inline attribute, and the third is the array representing the child elements.

As you can see from the body of the render function, it is a recursive call to render the top-level div#app;

$mount = Vue; $mount = Vue; $mount = Vue; $mount = Vue; Assign render and renderStaticFn to this.$options for the ue. Prototype. _render call to mount;