美文网首页
关于Vue渲染函数的一些总结

关于Vue渲染函数的一些总结

作者: 李牧敲代码 | 来源:发表于2019-03-12 16:17 被阅读0次

    最近遍历 Vue核心库的文档,看到渲染函数这章的时候,突然感觉眼前一亮。为什么这么说?因为从此刻开始Vue对于我们初学者来说开始由黑盒向灰盒转变了,我们见见可以开始看到Vue的一些本质的东西了。由于目前只是初步涉猎,先做一些基础总结,后期再补充。

    为什么要学习Vue的渲染函数?

    • 效率高。因为vue的模板最后还是要编译成渲染函数的。
    • 代码简洁 用JS能完成的代码行数会比模板语言的函数多?起码大多数情况是要少的!。

    效率高是因为vue的模板最后还是要编译成渲染函数的。为什么这么说,看下Vue的模板编译:

     <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    
    <body>
        <script src="https://cdn.bootcss.com/vue/2.6.8/vue.js"></script>
        <script>
            var res = Vue.compile(`<div>
                                        <header>
                                            <h1>I'm a template!</h1>
                                        </header>
                                        <p v-if="message">
                                            {{ message }}
                                        </p>
                                        <p v-else>
                                            No message.
                                        </p>
                                    </div>  `
                                );
            new Vue({
                data: {
                    msg: 'hello'
                },
                render: res.render,
                staticRenderFns: res.staticRenderFns,
                created() {
                    console.log(res);
                }
            })
        </script>
    </body>
    
    </html>
    
    
    image.png
    image.png
    可以看到,vue模板(html)被最后编译成了2部分,一部分是动态部分render, 另一部分被编译成了静态部分staticRenderFns,renderstaticRenderFns中的每个元素都是return了一个函数,这个函数就是渲染函数。既然模板编译最终都会编译成渲染函数,那么直接用渲染函数构建组件不是比用模板效率更高么?
    代码简洁, 这里引用官网的例子:
    加入我们要生成如下的锚点
    <h1>
      <a name="hello-world" href="#hello-world">
        Hello world!
      </a>
    </h1>
    

    我们要注册一个对应的组件

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

    然后在anchored-heading这个组件内部很可能会这样实现

      <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>
    //这边组件内部的其他代码省略
    

    是不是代码有点冗长?然后我们用渲染函数重新实现下上面的例子:

    //这个例子就是基于vuee/cli脚手架写的,这里注册了一个全局组件
    Vue.component('anchored-heading', {
        render(h) {
            return h(
                'h' + this.level,
                this.$slots.default
            );
        },
        props: {
            level: {
                default: 1
            }
        }
    })
    
    <!-- 调用刚才刚才全局注册的那个组件的地方 -->
    <template>
        <div>
           <anchored-heading :level="level">hello world</anchored-heading>
        </div>
    </template>
    
    <script>
        // @ is an alias to /src
        export default {
            data() {
                return {
                   level: 2 
                }           
            }
        }
    </script>
    

    是不是很简单?
    下面我们来详细看下这个渲染函数

        render(h) {
            return h(
                'h' + this.level, //HTML标签名, 必须
                {}, //一个包含模板相关属性的数据对象,可选
                this.$slots.default  //子节点对象数组,该数组的元素都是h函数生成的对象,可选
            );
        }
    

    就像上面写的那样渲染函数就是一个函数名为render的函数,该函数默认传入一个参数,该参数是一个用于创建虚拟节点(vnode)的方法h(Vue社区大家都命名该函数为h),接受3个参数,第一个参数接受HTML标签名,字符串类型,必须传入;第二个参数是一个包含模板相关属性的数据对象,可选传入;第三个参数是一个子节点对象数组,该数组的元素都是h函数生成的对象,可选传入。

    具体看下这个数据对象(照搬官网)

    有一点要注意:正如在模板语法中,v-bind:class 和 v-bind:style,会被特别对待一样,在 VNode 数据对象中,下列属性名是级别最高的字段。该对象也允许你绑定普通的 HTML 特性,就像 DOM 属性一样,比如 innerHTML (这会取代 v-html 指令)。

    {
      // 和`v-bind:class`一样的 API
      // 接收一个字符串、对象或字符串和对象组成的数组
      'class': {
        foo: true,
        bar: false
      },
      // 和`v-bind:style`一样的 API
      // 接收一个字符串、对象或对象组成的数组
      style: {
        color: 'red',
        fontSize: '14px'
      },
      // 普通的 HTML 特性
      attrs: {
        id: 'foo'
      },
      // 组件 props
      props: {
        myProp: 'bar'
      },
      // DOM 属性
      domProps: {
        innerHTML: 'baz'
      },
      // 事件监听器基于 `on`
      // 所以不再支持如 `v-on:keyup.enter` 修饰器
      // 需要手动匹配 keyCode。
      on: {
        click: this.clickHandler
      },
      // 仅用于组件,用于监听原生事件,而不是组件内部使用
      // `vm.$emit` 触发的事件。
      nativeOn: {
        click: this.nativeClickHandler
      },
      // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
      // 赋值,因为 Vue 已经自动为你进行了同步。
      directives: [
        {
          name: 'my-custom-directive',
          value: '2',
          expression: '1 + 1',
          arg: 'foo',
          modifiers: {
            bar: true
          }
        }
      ],
      // 作用域插槽格式
      // { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => createElement('span', props.text)
      },
      // 如果组件是其他组件的子组件,需为插槽指定名称
      slot: 'name-of-slot',
      // 其他特殊顶层属性
      key: 'myKey',
      ref: 'myRef',
      // 如果你在渲染函数中向多个元素都应用了相同的 ref 名,
      // 那么 `$refs.myRef` 会变成一个数组。
      refInFor: true
    }
    

    PS:上面说的h函数第三个参数中,子节点必须是唯一的,比如像下面这样创建组件是无效的

    render: function (createElement) {
      var myParagraphVNode = createElement('p', 'hi')
      return createElement('div', [
        // 错误-重复的 VNodes
        myParagraphVNode, myParagraphVNode
      ])
    }
    

    其他相关概念

    虚拟节点(vnode): 一个用于描述实际DOM对象的js对象,Vue就是值h函数返回的对象。
    虚拟DOM(vnode树):由虚拟节点构成的虚拟节点树称.

    【完】

    相关文章

      网友评论

          本文标题:关于Vue渲染函数的一些总结

          本文链接:https://www.haomeiwen.com/subject/yhfipqtx.html