foreplay
Before we get to the Vue Render function, we need to understand the overall flow of vue (as shown above).
From the figure above, you should be able to understand how a Vue component works.
- Templates are compiled to generate AST trees
- The AST tree generates Vue’s render function
- The render function combines data to generate a vNode(Virtual DOM Node) tree
- New UI after Diff and Patch (real DOM rendering)
In this diagram, we need to understand the following concepts:
- Template, Vue template is pure HTML, template syntax based on Vue, can be more convenient to deal with the relationship between data and UI interface
- AST, which is short for Abstract Syntax Tree, Vue parses HTML template into AST, and performs some optimized markup processing on AST to extract the largest static Tree, so that Virtual DOM can directly skip the following Diff
- The render function is used to generate the Virtual DOM. Vue recommends using templates to build our application, and in the underlying implementation Vue will eventually compile the templates into rendering functions. Therefore, if we want more control, we can write the render function directly.
- Virtual DOM
- Watcher, each Vue component has a corresponding
watcher
, it will be in the componentrender
And when the dependency is updated, the component is triggered to re-render, and Vue automatically optimizes and updates the DOM as needed
In the figure above, the render function can be used as a dividing line:
render
The left-hand side of the function is calledCompile timeTo convert the Vue board into a rendering functionrender
On the right side of the function is the Vue runtime, which mainly generates the Virtual DOM tree, Diff and Patch from the rendering function
render
Vue recommends using templates to create your HTML in most cases. However, in some scenarios, you really need the full programming power of JavaScript. In this case you can use render functions, which are closer to the compiler than templates.
Example of rendering titles
For example, here is an example of rendering titles on the official website
The implementation, you can look it up, I’m not going to go into detail here. The template implementation and render function implementation code are pasted here:
. Vue single file implementation
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
<script>
export default {
name: 'anchored-heading'.props: {
level: {
type: Number.required: true}}}</script>
Copy the code
Implementation of the Render function
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name Tag name
this.$slots.default // Array in child component)},props: {
level: {
type: Number.required: true}}})Copy the code
Isn’t that neat?
Node & tree & Virtual DOM
Now that we know the basic concepts and rendering functions of Vue, we need to know a little bit about how the browser works. This is important for learning about the render function. For example, the following HTML code:
<div>
<h1>My title</h1>
Some text content
<! --TODO: Add tagline -->
</div>
Copy the code
When the browser reads the code, it creates a DOM node tree to keep track of. If you were to draw a family tree to track the development of family members, the HTML DOM node tree might look something like this:
Every element and text is a node, and even comments are nodes. A node is part of a page, and like a family tree, each node can have child nodes.
It can be difficult to update all the nodes efficiently, but don’t worry, the Vue will do it for you automatically. You just need to tell the Vue what HTML is on the page.
It can be an HTML template, for example:
<h1>{{title}}</h1>
Copy the code
It can also be a render function:
render(h){
return h('h1'.this.title)
}
Copy the code
In both cases, Vue automatically keeps the page updated if the title value changes.
Virtual DOM
After compiling the templates, the Vue compiler compiles them into render functions, which return a virtual DOM tree when called.
Once we have the virtual DOM tree, we hand it over to a Patch function, which takes care of rendering the virtual DOM into the real DOM. In this process, Vue’s own responsive system detects the data sources it relies on during the rendering process, and when it detects the data source, it can accurately detect changes in the data source so that it can re-render when needed. After re-rendering, a new tree will be generated, and the new tree will be compared with the old tree to obtain the final change point that needs to be modified to the real DOM. Finally, the change will be implemented through the Patch function.
In simple terms, Vue compiles templates into virtual DOM rendering functions on the underlying implementation of Vue. Combined with Vue’s built-in response system, Vue can intelligently calculate the minimum cost of re-rendering components when the state should change and apply it to DOM operations.
Vue allows us to pass a JavaScript Object as component data via the data argument. Vue iterates over data Object properties, sets the description Object using the Object.defineProperty method, and intercepts reading and modifying the property via gett/setter functions.
Vue creates a Watcher layer to record properties as dependencies during component rendering, and when setters for dependencies are called, Watcher is notified to recalculate so that its associated components can be updated.
For Virtual DOM, if you want to have a deeper understanding, you can look at the Virtual DOM of Vue principle parsing
From the previous study, we learned that Vue keeps track of changes to the real DOM by creating a ** virtual DOM”. For example,
return createElement('h1'.this.title)
Copy the code
CreateElement, createNodeDescription, returns a Virtual Node, often abbreviated as “VNode”. The virtual DOM is the collective name of the entire VNode tree built from the Vue component tree.
The entire VNode tree created by the Vue component tree is unique and non-repeatable. For example, the render function below is invalid.
render(createElement) {
const vP = createElement('p'.'hello james')
return createElement('div'[// Error, there are duplicate vNodes
vP, vP
])
}
Copy the code
If you need a lot of duplicate components/elements, you can use factory functions. Such as:
render(createElement){
return createElement('div'.Array.apply(null, {length: 20}).map((a)= > {
return createElement('p'.'hi james')}}))Copy the code
Vue rendering mechanism
Here is a component rendering flow for a standalone build:
Two concepts of Vue are involved:
- Independent build, including template compiler, rendering process: HTML string => Render function => vNode => real DOM
- Build at runtime, no template compiler included, render process: Render function => vNode => real DOM
Packages built at runtime will have one less template compiler (and therefore run faster) than standalone packages. It is also different with the $mount function, which is the starting point in the rendering process, as illustrated by this flow chart:
As you can see from the image above, three templates are provided during the rendering process:
- Customize the render function
- template
- el
All render pages, which corresponds to the three ways we write Vue. All three modes end up with the render function.
For normal development, using template and EL is friendlier and easier to understand, but less flexible. The Render function, which is capable of more complex logic, has high flexibility, but is relatively poor for users to understand.
Customize the render function
Vue.component('anchored-heading', {
render(createElement) {
return createElement (
'h' + this.level,
this.$slots.default
)
},
props: {
level: {
type: Number.required: true}}})Copy the code
The template method
const app = new Vue({
template: `<div>{{ msg }}</div>`,
data () {
return {
msg: 'Hello Vue.js! '}}})Copy the code
El writing
let app = new Vue({
el: '#app',
data () {
return {
msg: 'Hello Vue! '}}})Copy the code
Understand & use the render function
createElement
CreateElement is a must when using the Render function.
CreateElement method parameter
CreateElement can accept multiple arguments
Parameter 1:{String | Object | Function }
Will pass
The first argument is mandatory. It can be a String, an Object, or a Function
// String
Vue.component('custom-element', {
render(createElement) {
return createElement('div'.'hello world! ')}})// Object
Vue.component('custom-element', {
render(createElement) {
return createElement({
template: `
hello world!
`})}})// Function
Vue.component('custom-element', {
render(createElement) {
const elFn = (a)= > { template: `
hello world!
` }
return createElement(elFn())
}
})
Copy the code
The above code is equivalent to:
<template>
<div>hello world!</>
</template>
<script>
export default {
name: 'custom-element'
}
</script>
Copy the code
Parameter 2:{ Object }
optional
The second parameter to createElemen is optional. This parameter is an Object, for example:
Vue.component('custom-element', {
render(createElement) {
const self = this;
return createElement('div', {
'class': {
foo: true.bar: false
},
style: {
color: 'red'.fontSize: '18px'
},
attrs: {
...self.attrs,
id: 'id-demo'
},
on: {
...self.$listeners,
click: (e) = > {console.log(e)}
},
domProps: {
innerHTML: 'hello world! '
},
staticClass: 'wrapper'})}})Copy the code
Is equivalent to:
<template>
<div :id="id" class="wrapper" :class="{'foo': true, 'bar': false}" :style="{color: 'red', fontSize: '18px'}" v-bind="$attrs" v-on="$listeners" @click="(e) => console.log(e)"> hello world! </div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
id: 'id-demo'}}}</script>
<style>
.wrapper{
display: block;
width: 100%;
}
</style>
Copy the code
Parameter 3:{ String | Array }
optional
The third parameter to createElement is optional and can be passed a String or Array, for example:
Vue.component('custom-element', {
render (createElement) {
var self = this
return createElement(
'div',
{
class: {
title: true
},
style: {
border: '1px solid'.padding: '10px'
}
},
[
createElement('h1'.'Hello Vue! '),
createElement('p'.'Hello world! '])}})Copy the code
Is equivalent to:
<template>
<div :class="{'title': true}" :style="{border: '1px solid', padding: '10px'}">
<h1>Hello Vue!</h1>
<p>Hello world!</p>
</div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
id: 'id-demo'}}}</script>
Copy the code
Create a component with the same effect using Template and Render
The template method
<template>
<div id="wrapper" :class="{show: show}" @click="clickHandler">
Hello Vue!
</div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
show: true}},methods: {
clickHandler(){
console.log('you had click me! '); }}}</script>
Copy the code
Render way
Vue.component('custom-element', {
data () {
return {
show: true}},methods: {
clickHandler: function(){
console.log('you had click me! '); }},render: function (createElement) {
return createElement('div', {
class: {
show: this.show
},
attrs: {
id: 'wrapper'
},
on: {
click: this.handleClick
}
}, 'Hello Vue! ')}})Copy the code
CreateElement parsing procedure
CreateElement method analytical flow chart (pick to: segmentfault.com/a/119000000…).
CreateElement method core source code parsing process (need to have a certain strength to JS, pick to: segmentfault.com/a/119000000…).
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {
// Data is not transmitted
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
// If alwaysNormalize is true
// normalizationType should be set to the constant value of ALWAYS_NORMALIZE
if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
Call _createElement to create a virtual node
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (context, tag, data, children, normalizationType) {
/** * If data.__ob__ is present, data is observed by the Observer * cannot be used as virtual node data * needs to throw a warning and return an empty node ** Monitored data cannot be used as vNode rendered data for the following reasons: * Data may be changed during vNode rendering, which triggers monitoring and results in undesired operations */
if(data && data.__ob__) { process.env.NODE_ENV ! = ='production' && warn(
`Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render! ',
context
)
return createEmptyVNode()
}
// When the component's IS property is set to a falsy value
// Vue will not know what to render this component into
// Render an empty node
if(! tag) {return createEmptyVNode()
}
// Scope slot
if (Array.isArray(children) && typeof children[0= = ='function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// Depending on the value of normalizationType, select different processing methods
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// If the label name is a string
if (typeof tag === 'string') {
let Ctor
// Get the namespace of the tag name
ns = config.getTagNamespace(tag)
// Check whether the label is reserved
if (config.isReservedTag(tag)) {
// If the label is reserved, create such a vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined.undefined, context
)
// If the label is not reserved, then we will try to find the definition of the label from the COMPONENTS of the VM
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
// If the label definition is found, the virtual component node is created with it
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// Create a vnode
vnode = new VNode(
tag, data, children,
undefined.undefined, context
)
}
When a tag is not a string, we consider it to be the component's constructor class
// Create it directly
} else {
vnode = createComponent(tag, data, context, children)
}
// If there are vNodes
if (vnode) {
// If there is a namespace, apply the namespace and return vNode
if (ns) applyNS(vnode, ns)
return vnode
// Otherwise, return an empty node
} else {
return createEmptyVNode()
}
}
}
Copy the code
Use the Render function instead of the template function
When using Vue templates, we can flexibly use template syntax such as V-if, V-for, V-model, and
in templates. However, there is no dedicated API provided in the Render function. If you use this in Render, you need to use native JavaScript to do it.
v-if
& v-for
<ul v-if="items.length">
<li v-for="item in items">{{ item }}</li>
</ul>
<p v-else>No items found.</p>
Copy the code
Render function implementation
Vue.component('item-list', {props: ['items'],
render (createElement) {
if (this.items.length) {
return createElement('ul'.this.items.map((item) = > {
return createElement('item')}}))else {
return createElement('p'.'No items found.')}}})Copy the code
v-model
<template>
<el-input :name="name" @input="val => name = val"></el-input>
</template>
<script>
export default {
name: 'app',
data(){
return {
name: 'hello vue.js'}}}</script>
Copy the code
Render function implementation
Vue.component('app', {
data(){
return {
name: 'hello vue.js'}},render: function (createElement) {
var self = this
return createElement('el-input', {
domProps: {
value: self.name
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
},
props: {
name: String}})Copy the code
slot
In Vue, you can do this by:
this.$slots
Gets the static content of the VNodes list.
render(h){
return h('div'.this.$slots.default)
}
Copy the code
Is equivalent to:
<template>
<div>
<slot> </slot>
</div>
</template>
Copy the code
In Vue, you can do this by:
this.$scopedSlots
Gets the scope slot that can be used as a function that returns VNodes
props: ['message'],
render (createElement) {
// `<div><slot :text="message"></slot></div>`
return createElement('div'[this.$scopedSlots.default({
text: this.message
})
])
}
Copy the code
To pass a scope slot to a child component using a render function, you can use the scopedSlots field in the VNode data:
<div id="app">
<custom-ele></custom-ele>
</div>
Copy the code
Vue.component('custom-ele', {
render: function (createElement) {
return createElement('div', [
createElement('child', {
scopedSlots: {
default: function (props) {
return [
createElement('span'.'From Parent Component'),
createElement('span', props.text)
]
}
}
})
])
}
})
Vue.component('child', {
render: function (createElement) {
return createElement('strong'.this.$scopedSlots.default({
text: 'This is Child Component'}}})))let app = new Vue({
el: '#app'
}
Copy the code
JSX
If you’re used to template writing and then have to use the render function, it’s going to be pretty uncomfortable, especially with complex components. But using JSX in Vue brings us back to a more template-like syntax.
import View from './View.vue'
new Vue({
el: '#demo',
render (h) {
return (
<View level={1}>
<span>Hello</span> world!
</View>)}}Copy the code
Using H as an alias for createElement is a common convention in the Vue ecosystem, and is actually required by JSX to trigger an error in the application if h is disabled in scope.
conclusion
The key steps in Vue rendering are:
new Vue
To perform initialization- mount
$mount
, through customizationrender
Method,template
.el
Such as generationrender
Rendering function - through
Watcher
Listen for changes in data - When the data changes,
render
The function executes to generate a VNode object - through
patch
Method to compare the old and new VNode objects throughDOM Diff
Algorithms to add/modify/remove real DOM elements
At this point, the entire New Vue rendering process is complete.
A link to the
- Vue-jsx – Render title example
- The DOM node tree
- Virtual DOM of Vue principle analysis
- vue-render-jameszhang