The biggest difference between Vue.js 2.x and Vue.js 1.x is that 2.x uses Virtual DOM (virtual DOM) to update DOM nodes and improve rendering performance.
Generally speaking, when we write Vue.js components, templates are written in , but it is not the final content. Template is just a developer-friendly syntax that can You can see DOM nodes at a glance, which is easy to maintain. During the Vue.js compilation phase, it will be parsed into Virtual DOM.
Compared with DOM operations, Virtual DOM is calculated based on JavaScript, so the overhead will be much smaller. The following figure demonstrates the process of running Virtual DOM:
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-gQrV5d8U-1679449218977)(null)]
A normal DOM node looks like this in HTML:
<div id="main"> <p>text content</p> <p>text content</p> </div>
JavaScript objects created with Virtual DOM typically look like this:
const vNode = {<!-- --> tag: 'div', attributes: {<!-- --> id: 'main' }, children: [ // p node ] }
The vNode object describes the real DOM structure with some specific options.
In Vue.js, for most scenarios, using a template is sufficient, but if you want to fully utilize the programming capabilities of JavaScript, or in some specific scenarios (described later), you need to use the Render function of Vue.js.
Render function
Just like the Virtual DOM example introduced above, the Render function of Vue.js has a similar syntax, requiring some specific options to rewrite the content of the template into a JavaScript object.
For junior front-end engineers, or those who want to quickly build a website, it is more difficult to directly use the Render function to develop Vue.js components than the template. The reason is that the Render function returns a JS object without the traditional DOM hierarchy. With if, else, for and other statements, split the nodes into different JS objects and then assemble them. If the template is complex, the Render function is difficult to read and maintain. Therefore, for most component development and business development, we can directly use the template syntax, and do not need to use the Render function specially, which will only increase the burden, and also give up the biggest advantage of Vue.js (React has no template syntax) .
Many developers who learn Vue.js “dodge” or give up this part when they encounter the Render function. This is not a problem, because excellent Vue.js programs can still be written without the Render function. However, the Render function is not as complicated as imagined, but there are so many configuration items that it is difficult to remember for a while, but in the final analysis, the Render function only has 3 parameters.
Let’s look at a set of comparisons between template and Render:
<template> <div id="main" class="container" style="color: red"> <p v-if="show">Content 1</p> <p v-else>Content 2</p> </div> </template> <script> export default {<!-- --> data () {<!-- --> return {<!-- --> show: false } } } </script>
export default {<!-- --> data () {<!-- --> return {<!-- --> show: false } }, render: (h) => {<!-- --> let childNode; if (this. show) {<!-- --> childNode = h('p', 'Content 1'); } else {<!-- --> childNode = h('p', 'content 2'); } return h('div', {<!-- --> attrs: {<!-- --> id: 'main' }, class: {<!-- --> container: true }, style: {<!-- --> color: 'red' } }, [childNode]); } }
Here h
, namely createElement
, is the core of the Render function. It can be seen that v-if / v-else and other instructions in the template are replaced by JS if / else, then v-for strong> will naturally be replaced by the for statement.
h has 3 parameters, namely:
-
The element or component to be rendered, which can be an html tag, component option or a function (uncommonly used), this parameter is required. Example:
// 1. html tags h('div'); // 2. Component options import DatePicker from '../component/date-picker.vue'; h(DatePicker);
-
Data objects corresponding to attributes, such as component props, element class, bound events, slots, custom instructions, etc. This parameter is optional. The Render configuration items mentioned above refer to this parameter. For the complete configuration and examples of this parameter, you can view it in the Vue.js documentation. It is not necessary to remember all of them, just refer to it when you use it: createElement parameter.
-
child node, optional, String or Array, which is also an h. Example:
[ 'content', h('p', 'content'), h(Component, {<!-- --> props: {<!-- --> someProp: 'foo' } }) ]
Constraint
In all component trees, if the vNode is a component or a slot containing components, then the vNode must be unique. Both of the following examples are wrong:
// Partial declaration component const Child = {<!-- --> render: (h) => {<!-- --> return h('p', 'text'); } } export default {<!-- --> render: (h) => {<!-- --> // Create a child node, use the component Child const ChildNode = h(Child); return h('div', [ ChildNode, ChildNode ]); } }
{<!-- --> render: (h) => {<!-- --> return h('div', [ this.$slots.default, this. $slots. default ]) } }
Repeatedly rendering multiple components or elements can be solved with a loop and factory function:
const Child = {<!-- --> render: (h) => {<!-- --> return h('p', 'text'); } } export default {<!-- --> render: (h) => {<!-- --> const children = Array.apply(null, {<!-- --> length: 5 }).map(() => {<!-- --> return h(Child); }); return h('div', children); } }
For slots containing components, reuse is more complicated, and each child node of the slot needs to be cloned, for example:
{<!-- --> render: (h) => {<!-- --> function cloneVNode (vnode) {<!-- --> // Recursively traverse all child nodes and clone const clonedChildren = vnode.children & amp; & amp; vnode.children.map(vnode => cloneVNode(vnode)); const cloned = h(vnode.tag, vnode.data, clonedChildren); cloned.text = vnode.text; cloned.isComment = vnode.isComment; cloned.componentOptions = vnode.componentOptions; cloned.elm = vnode.elm; cloned.context = vnode.context; cloned.ns = vnode.ns; cloned.isStatic = vnode.isStatic; cloned.key = vnode.key; return cloned; } const vNodes = this.$slots.default === undefined ? [] : this.$slots.default; const clonedVNodes = this.$slots.default === undefined ? [] : vNodes.map(vnode => cloneVNode(vnode)); return h('div', [ vNodes, clonedVNodes ]) } }
In the Render function, a cloneVNode factory function is created, and all child nodes of the slot are cloned recursively, and the key attributes of the VNode are also copied.
Deep clone slot is not a built-in method of Vue.js, nor is it recommended. It is a black technology, and it is only used in some special scenarios, and it is almost not used in normal business. For example, the shuttle frame component Transfer of the iView component library uses this method:
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-KDo7vOoS-1679449219020)(null)]
Its usage is:
<Transfer :data="data" :target-keys="targetKeys" :render-format="renderFormat"> <div :style="{float: 'right', margin: '5px'}"> <Button size="small" @click="reloadMockData">Refresh</Button> </div> </Transfer>
The default slot in the example is a Refresh button, which the user only writes once, but in the Transfer component, it is displayed twice by cloning the VNode. If you don’t do this, you have to declare two named slots, but the logic of the left and right may be exactly the same, and the user has to write two identical slots, which is not friendly.
There are many other basic uses of the Render function, such as the usage of v-model, events and modifiers, slots, etc. Readers can go to the Vue.js documentation to read. Vue.js render function
Render function usage scenarios
As mentioned above, in general, it is not recommended to use the Render function directly. It is enough to use the template. In Vue.js, there are mainly the following four points when using the Render function:
-
Use two identical slots. In the template, Vue.js does not allow the use of two identical slots, such as the following example is wrong:
<template> <div> <slot></slot> <slot></slot> </div> </template>
The solution is the constraint mentioned above, using a method of deep cloning VNode nodes.
-
In the SSR environment (server-side rendering), if it is not a conventional template writing method, such as the component instance generated by Vue.extend and new Vue construction, it cannot be compiled, which is also introduced in the previous section. Looking back at the notification.js file of the
$Alert
component in the previous section, the Render function was used to render the Alert component at that time. If it was changed to another way of writing, an error would be reported in SSR. Compare the two ways of writing:// correct writing import Alert from './alert.vue'; import Vue from 'vue'; Alert.newInstance = properties => {<!-- --> const props = properties || {<!-- -->}; const Instance = new Vue({<!-- --> data: props, render (h) {<!-- --> return h(Alert, {<!-- --> props:props }); } }); const component = Instance. $mount(); document.body.appendChild(component.$el); const alert = Instance. $children[0]; return {<!-- --> add (noticeProps) {<!-- --> alert. add(noticeProps); }, remove (name) {<!-- --> alert. remove(name); } } }; export default Alert;
// How to write an error under SSR import Alert from './alert.vue'; import Vue from 'vue'; Alert.newInstance = properties => {<!-- --> const props = properties || {<!-- -->}; const div = document. createElement('div'); div.innerHTML = `<Alert ${<!-- -->props}></Alert>`; document.body.appendChild(div); const Instance = new Vue({<!-- --> el: div, data: props, components: {<!-- --> Alert } }); const alert = Instance. $children[0]; return {<!-- --> add (noticeProps) {<!-- --> alert. add(noticeProps); }, remove (name) {<!-- --> alert. remove(name); } } }; export default Alert;
-
In the runtime version of Vue.js, if you use Vue.extend to manually construct an instance, using the template option will cause an error, which is also introduced in Section 9. The solution is also very simple, just rewrite the template as Render. It should be noted that when developing independent components, you can configure the Vue.js version to make the template option available, but this is in your own environment, and the user’s Vue.js version cannot be guaranteed, so for components provided to others , it is necessary to consider the compatibility of the runtime version and the SSR environment.
-
This is probably the most important point of using the Render function. For a Vue.js component, part of the content needs to be passed from the parent to be displayed. If it is text or the like, it can be directly passed through
props
. If the content has a style or a more complicated html structure, You can use thev-html
command to render, and the parent pass is still an HTML Element string, but it can only parse normal html nodes and has the risk of XSS. When you need to customize the display content to the maximum extent, you need theRender
function, which can render a complete Vue.js component. You might say, wouldn’t it be good to use slots? Indeed, the role of slots is to distribute content, but in some special components, slots may not work either. For example, a table componentTable
, it only receives two props: column configuration columns and row data data, but the cells of a certain column are not as simple as just displaying data, and may contain some complex Operation, this kind of scene is not enough to use only slots, and there is no way to determine which column of slots it is. There are two solutions for this scenario, one is the Render function, and the actual combat in the next section is to develop such a Table component; the other is to use the scope slot (slot-scope), which will be introduced in detail in the next section.
Functional Render
Vue.js provides a functional
Boolean option, set to true to make the component stateless and instanceless, that is, there is no data and this context. In this way, using the Render function to return virtual nodes can be rendered more easily, because the functional component (Functional Render) is just a function, and the rendering overhead is much smaller.
With functional components, the Render function provides a second parameter context to provide a temporary context. The data, props, slots, children, and parents required by the component are all passed through this context. For example, this.level should be rewritten as context.props.level, and this.$slots.default should be rewritten as context.children.
You can read the Vue.js documentation – Functional Components to see examples.
Functional components are not very commonly used in business, and there are similar methods to implement them. For example, in some scenarios, the is feature can be used to dynamically mount components. Functional components are mainly applicable to the following two scenarios:
- Programmatically select one of several components;
- Manipulate children, props, data before passing them to child components.
For example, as mentioned above, a component needs to be customized using the Render function instead of passing ordinary text or v-html instructions. At this time, Functional Render can be used. See the following example:
-
First create a functional component render.js:
// render.js export default {<!-- --> functional: true, props: {<!-- --> render: Function }, render: (h, ctx) => {<!-- --> return ctx.props.render(h); } };
It only defines one props: render, the format is Function, because it is functional, so the second parameter
ctx
is used in render to get props. This is an intermediate file and can be reused. When other components need this function, they can import it. -
Create components:
<!-- my-component.vue --> <template> <div> <Render :render="render"></Render> </div> </template> <script> import Render from './render.js'; export default {<!-- --> components: {<!-- --> Render }, props: {<!-- --> render: Function } } </script>
-
Using the my-compoennt component above:
<!-- demo.vue --> <template> <div> <my-component:render="render"></my-component> </div> </template> <script> import myComponent from '../components/my-component.vue'; export default {<!-- --> components: {<!-- --> myComponent }, data () {<!-- --> return {<!-- --> render: (h) => {<!-- --> return h('div', {<!-- --> style: {<!-- --> color: 'red' } }, 'Custom Content'); } } } } </script>
The render.js here is just to adopt the Render content in demo.vue and has no other use, so Functional Render is used.
In this example, slot can be used instead of Functional Render, because there is only one prop render
. If the
in the example is generated by v-for
, that is, when there are multiple, it cannot be realized with one slot, then it is very easy to use the Render function It is convenient, and will be introduced in the next chapter.
Conclusion
If you want to write Vue.js in a different way, try the Render function, it will make you “love and hate”!
Note: Part of the content in this section refers to “Vue.js in Action” (Tsinghua University Press), and part of the code refers to iView.