Rander in Vue (rendering functions & JSX)

I believe that everyone has seen or used the lander in code more or less. If you are still confused about it, get on the bus! I'll take you to the plate today.

@[toc]

I. Rander's profile

Render function is a new function of Vue2.x, which is mainly used to improve the performance of nodes. It is based on JavaScript calculation. Use the render function to parse the nodes in the Template into virtual Dom.

Vue recommends using templates to create your HTML in most cases. In some scenarios, however, you really need the full programming power of JavaScript. At this point you can use the rendering function, which is closer to the compiler than the template.

Simply put, in Vue, we use template HTML syntax to build pages. Using Render function, we can use Js language to build DOM.

Because Vue is a virtual DOM, when you get the Template template, you also need to translate it into the function of VNode. When you use Render function to build DOM, Vue will not need to translate.

The first meeting with Rander

The first time you meet it, it may be like this:

  • IView
render:(h, params)=>{
    return h('div', {style:{width:'100px',height:'100px',background:'#ccc '}},' place ')
}
  • Element
<el-table-column :render-header="setHeader">
</el-table-column>
setHeader (h) {
 return h('span', [
    h('span', { style: 'line-height: 40px;' }, 'Remarks'),
      h('el-button', {
        props: { type: 'primary', size: 'medium', disabled: this.isDisable || !this.tableData.length },
        on: { click: this.save }
      }, 'Save current page')
    ])
  ])
},

Or something like this:

renderContent (createElement, { node, data, store }) {
    return createElement('span', [
        // Display node information of the tree
        createElement('span', node.label)
        // ......
    ])
}

What is its real body like? It starts with its life experience.

2.1 nodes and trees

It's important to understand how some browsers work before delving into rendering functions. Take the following HTML as an example:

<div>
  <h1>My title</h1>
  Some text content
  <!-- TODO: Add tagline -->
</div>

When the browser reads this code, it creates a DOM node tree to keep track of everything, just as you would draw a family tree to track the development of family members.

The DOM node tree corresponding to the above HTML is shown in the following figure:

Each element is a node. Each paragraph of text is also a node. Even annotations are nodes. A node is a part of the page. Just like a family tree, each node can have child nodes (that is, each part can contain other parts).

Updating all these nodes efficiently can be difficult, but fortunately you don't have to do it manually. You just need to tell Vue what you want the HTML on the page, which can be in a template:

<h1>{{ blogTitle }}</h1>

Or in a rendering function:

render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

In both cases, Vue automatically keeps the page updated, even if the blogTitle changes.

2.2 virtual DOM

Vue tracks how it changes the real DOM by creating a virtual dom. Take a closer look at this line of code:

return createElement('h1', this.blogTitle)

What exactly will createElement return? It's not a real DOM element. Its more accurate name might be createNodeDescription, because the information it contains tells the Vue page what kind of node to render, including the description of its child nodes. We describe such a node as "virtual node" and often abbreviate it as "VNode". "Virtual DOM" is our name for the entire VNode tree established by the Vue component tree.

Note: = = when using the render function to describe the virtual DOM, Vue provides a function, which is the tool needed to build the virtual dom. He was named createElement on the official website. There is also a contracted abbreviation called h. It is a general practice in Vue ecosystem to use h as the alias of createElement, which is actually required by JSX. = =

Interesting ~ in fact, it's createElement. Let's get closer to it and learn more about it~

3. Date with Rander

3.1 createElement parameter

createElement (TagName, Option, Content) takes three parameters
createElement("defined element", {nature of element}, "content of element" / [content of element])

  • Official documents
// @returns {VNode}
createElement(
  // {String | Object | Function}
  // An HTML tag name, component option object, or
  // resolve an async function of any of the above. Required items.
  'div',

  // {Object}
  // A data object corresponding to the attribute in the template. Optional.
  {
    // (see the next section-3.2 in depth data object for details)
  },

  // {String | Array}
  // The child virtual nodes are built by 'createElement()',
  // You can also use strings to generate text virtual nodes. Optional.
  [
    'Write some words first',
    createElement('h1', 'A headline'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

3.2 in depth data objects

{
  // The API of 'v-bind:class' is the same,
  // Accepts a string, object, or array of strings and objects
  'class': {
    foo: true,
    bar: false
  },
  // The API of 'v-bind:style' is the same,
  // Accepts a string, object, or array of objects
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // Common HTML features
  attrs: {
    id: 'foo'
  },
  // Component prop
  props: {
    myProp: 'bar'
  },
  // DOM attribute
  domProps: {
    innerHTML: 'baz'
  },
  // The event listener is in the 'on' attribute,
  // However, modifiers such as' v-on:keyup.enter 'are no longer supported.
  // The keyCode needs to be checked manually in the processing function.
  on: {
    click: this.clickHandler
  },
  // Only for components, listening to native events, not internal use of components
  // `Event triggered by vm.$emit '.
  nativeOn: {
    click: this.nativeClickHandler
  },
  // Custom instructions. Note that you can't use 'oldValue' in 'binding'`
  // Assign a value because Vue has automatically synchronized it for you.
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // The format of the scope slot is
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // If the component is a sub component of another component, specify a name for the slot
  slot: 'name-of-slot',
  // Other special top-level properties
  key: 'myKey',
  ref: 'myRef',
  // If you apply the same ref name to more than one element in the rendering function,
  // Then ` $refs.myRef 'will become an array.
  refInFor: true
}

3.3 take a small chestnut

render:(h) => {
  return h('div',{
   //Bind value attribute to div
     props: {
         value:''
     },
   //Binding styles to div
   style:{
     width:'30px'
   }, 
   //Bind click event to div
     on: {
         click: () => {
            console.log('Click events')
         }
     },
  })
}

3.4 constraint

It's also grumpy. Remember the constraint~

  • VNode must be unique

All vnodes in the component tree must be unique. This means that the following rendering functions are illegal:

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // Error - duplicate VNode
    myParagraphVNode, myParagraphVNode
  ])
}

If you really need to repeat elements / components many times, you can use factory functions. For example, the following rendering function renders 20 identical paragraphs in a perfectly legal way:

render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}

The above is the foundation. Please remember. Here are some of its features

4. Rander's personality

4.1 v-if and v-for

As long as the operation can be easily completed in native JavaScript, Vue's rendering function will not provide a proprietary alternative. For example, v-if and v-for used in templates:

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>

These can be rewritten in the rendering function with JavaScript if/else and map:

props: ['items'],
render: function (createElement) {
  if (this.items.length) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p', 'No items found.')
  }
}

4.2 v-model

There is no direct correspondence with v-model in the rendering function -- you must implement the corresponding logic yourself:

props: ['value'],
render: function (createElement) {
  var self = this
  return createElement('input', {
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}

That's the price of going deep into the bottom, but it gives you better control over the details of the interaction than v-model.

4.3 event & key modifier

For event modifiers. passive,. capture, and. once, Vue provides a prefix that can be used for on:

Modifier prefix
.passive &
.capture !
.once ~
. capture.once or. once.capture ~!

For example:

on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

For all other modifiers, private prefixes are not required because you can use event methods in event handlers:

Modifier Dealing with equivalent operations in functions
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target !== event.currentTarget) return
Key:. Enter,. 13 If (event. Keycode! = = 13) return (for other key modifiers, change 13 to another key code)
Modifier key:. Ctrl,. Alt,. Shift,. Meta If (! Event. Ctrl key) return (change Ctrl key to altKey, shiftKey or metakey respectively)

Here is an example of using all modifiers:

on: {
  keyup: function (event) {
    // If the element triggering the event is not an event bound element
    // Then return
    if (event.target !== event.currentTarget) return
    // If you don't press the enter key or
    // shift is not pressed at the same time
    // Then return
    if (!event.shiftKey || event.keyCode !== 13) return
    // Prevent events from bubbling
    event.stopPropagation()
    // Block the element's default keyup event
    event.preventDefault()
    // ...
  }
}

4.4 slot

You can access the contents of static slots through this.$slots. Each slot is a VNode array:

render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div', this.$slots.default)
}

You can also access the scope slots through this.$scopedSlots. Each scope slot is a function that returns several vnodes:

props: ['message'],
render: function (createElement) {
  // `<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

If you want to pass scope slots to subcomponents with rendering functions, you can use the scopedSlots field in the VNode data object:

render: function (createElement) {
  return createElement('div', [
    createElement('child', {
      // Pass in data objects ` scopedSlots`
      // The format is {Name: props = > vnode | array < vnode >}
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}

Five, actual combat

Tree in Element

// Rendering callback of content area of tree node
renderContent(h, { node, data, store }) {
    let aa = () => {
        console.log(data)
    }
    return  h('span', [
        h('span', {
            class: "custom-tree-node"
        }, [
            h('i', { class: "icon-folder" }), h('span', { props: { title: node.label }, class: "text ellipsis" }, node.label),
            h('el-popover', {
                props: {
                    placement: "bottom",
                    title: "",
                    width: "61",
                    popperClass: "option-group-popover",
                    trigger: "hover"
                }
            }, [
                h('ul', { class: "option-group" }, [
                    h('li', {
                        class: "pointer-text",
                        on: {
                            click: aa
                        }
                    }, 'edit'),
                    h('li', { class: "pointer-text" }, 'delete'),
                    h('li', { class: "pointer-text" }, 'Add to')
                ]),
                h('i', { slot: "reference", class: "el-icon-more fr more-icon",
                    on: {
                        click: (e) => {
                            e.stopPropagation();
                        }
                    }
                })
            ])
        ])
    ])
},

Vi. extension - JSX

If you write many render functions, you may find the following code very painful:

createElement(
  'anchored-heading', {
    props: {
      level: 1
    }
  }, [
    createElement('span', 'Hello'),
    ' world!'
  ]
)

Especially when the corresponding template is so simple:

<anchored-heading :level="1">
  <span>Hello</span> world!
</anchored-heading>

That's why there's a Babel plug-in for using JSX syntax in Vue, which brings us back to a more template like syntax.

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

It's not easy to code. I think it's helpful. I'd like to ask you for your support~

Scan the QR code above to follow my subscription number~

I think it's helpful for you. Please give me a compliment~

Keywords: Javascript Vue Attribute less

Added by jimmyo on Mon, 18 Nov 2019 10:13:24 +0200