zoukankan      html  css  js  c++  java
  • 理解Vue中的Render渲染函数

    理解Vue中的Render渲染函数

    VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数。
    比如如下我想要实现如下html:

    <div id="container">
      <h1>
        <a href="#">
          Hello world!
        </a>
      </h1>
    </div>

    我们会如下使用:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading :level="1">
            <a href="#">Hello world!</a>
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script type="text/x-template" id="templateId">
        <h1 v-if="level === 1">
          <slot></slot>
        </h1>
        <h2 v-else-if="level === 2">
          <slot></slot>
        </h2>
      </script>
    
      <script>
        Vue.component('tb-heading', {
          template: '#templateId',
          props: {
            level: {
              type: Number,
              required: true
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    查看效果

    如上代码是根据参数 :level来显示不同级别的标题中插入锚点元素,我们需要重复的使用 <slot></slot>.

    下面我们来尝试使用 render函数重写上面的demo;如下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading :level="2">
            <a href="#">Hello world!</a>
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('tb-heading', {
          render: function(createElement) {
            return createElement(
              'h' + this.level,    // tag name 标签名称
              this.$slots.default  // 组件的子元素
            )
          },
          props: {
            level: {
              type: Number,
              required: true
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    查看效果

    如上 render函数代码看起来非常简单就实现了,组件中的子元素存储在组件实列中 $slots.default 中。

    理解createElement
    Vue通过建立一个虚拟DOM对真实的DOM发生变化保存追踪,如下代码:
    return createElement('h1', this.title);
    createElement返回的是包含的信息会告诉VUE页面上需要渲染什么样的节点及其子节点。我们称这样的节点为虚拟DOM,可以简写为VNode,

    createElement 参数
    
    // @return {VNode}
    createElement(
      // {String | Object | Function}
      // 一个HTML标签字符串,组件选项对象,或者一个返回值类型为String/Object的函数。该参数是必须的
      'div',
    
      // {Object}
      // 一个包含模板相关属性的数据对象,这样我们可以在template中使用这些属性,该参数是可选的。
      {
    
      },
    
      // {String | Array}
      // 子节点(VNodes)由 createElement() 构建而成。可选参数
      // 或简单的使用字符串来生成的 "文本节点"。
      [
        'xxxx',
        createElement('h1', '一则头条'),
        createElement(MyComponent, {
          props: {
            someProp: 'xxx'
          }
        })
      ]
    )

    理解深入data对象。
    在模板语法中,我们可以使用 v-bind:class 和 v-bind:style 来绑定属性,在VNode数据对象中,下面的属性名的字段级别是最高的。
    该对象允许我们绑定普通的html特性,就像DOM属性一样。如下:

    {
      // 和`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
      },
      // 自定义指令。注意事项:不能对绑定的旧值设值
      // Vue 会为您持续追踪
      directives: [
        {
          name: 'my-custom-directive',
          value: '2',
          expression: '1 + 1',
          arg: 'foo',
          modifiers: {
            bar: true
          }
        }
      ],
      // Scoped slots in the form of
      // { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => createElement('span', props.text)
      },
      // 如果组件是其他组件的子组件,需为插槽指定名称
      slot: 'name-of-slot',
      // 其他特殊顶层属性
      key: 'myKey',
      ref: 'myRef'
    }

    上面的data数据可能不太好理解,我们来看一个demo,就知道它是如何使用的了,如下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading :level="2">
            Hello world!
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        var getChildrenTextContent = function(children) {
          return children.map(function(node) {
            return node.children ? getChildrenTextContent(node.children) : node.text
          }).join('')
        };
        Vue.component('tb-heading', {
          render: function(createElement) {
            var headingId = getChildrenTextContent(this.$slots.default)
              .toLowerCase()
              .replace(/W+/g, '-')
              .replace(/(^-|-$)/g, '')
            return createElement(
              'h' + this.level,
              [
                createElement('a', {
                  attrs: {
                    name: headingId,
                    href: '#' + headingId
                  },
                  style: {
                    color: 'red',
                    fontSize: '20px'
                  },
                  'class': {
                    foo: true,
                    bar: false
                  },
                  // DOM属性
                  domProps: {
                    innerHTML: 'baz'
                  },
                  // 组件props
                  props: {
                    myProp: 'bar'
                  },
                  // 事件监听基于 'on'
                  // 所以不再支持如 'v-on:keyup.enter' 修饰语
                  // 需要手动匹配 KeyCode  
                  on: {
                    click: function(event) {
                      event.preventDefault();
                      console.log(111);
                    }
                  }
                }, this.$slots.default)
              ]
            )
          },
          props: {
            level: {
              type: Number,
              required: true
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    对应的属性使用方法和上面一样既可以了,我们可以打开页面查看下效果也是可以的。如下

    查看效果

    VNodes 不一定必须唯一 (文档中说要唯一)
    文档中说 VNode必须唯一;说 下面的 render function 是无效的:
    但是我通过测试时可以的,如下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading :level="2">
            Hello world!
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('tb-heading', {
          render: function(createElement) {
            var pElem = createElement('p', 'hello world');
            return createElement('div', [
              pElem, pElem
            ])
          },
          props: {
            level: {
              type: Number,
              required: true
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    查看效果

    使用Javascript代替模板功能
     v-if 和 v-for
    template 中有 v-if 和 v-for, 但是vue中的render函数没有提供专用的API。
    比如如下:

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

    在render函数中会被javascript的 if/else 和map重新实现。如下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading>
            Hello world!
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('tb-heading', {
          render: function(createElement) {
            console.log(this)
            if (this.items.length) {
              return createElement('ul', this.items.map(function(item){
                return createElement('li', item.name);
              }))
            } else {
              return createElement('p', 'No items found.');
            }
          },
          
          props: {
            items: {
              type: Array,
              default: function() {
                return [
                  {
                    name: 'kongzhi1'
                  },
                  {
                    name: 'kongzhi2'
                  }
                ]
              }
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    查看效果

    v-model

    render函数中没有 与 v-model相应的api,我们必须自己来实现相应的逻辑。如下代码可以实现:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading @input="inputFunc">
            Hello world!
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('tb-heading', {
          render: function(createElement) {
            var self = this;
            return createElement('input', {
              domProps: {
                value: '11'
              },
              on: {
                input: function(event) {
                  self.value = event.target.value;
                  self.$emit('input', self.value);
                }
              }
            })
          },
          props: {
            
          }
        });
        new Vue({
          el: '#container',
          methods: {
            inputFunc: function(value) {
              console.log(value)
            }
          }
        });
      </script>
    </html>

    查看效果

    理解插槽

    可以从 this.$slots 获取VNodes列表中的静态内容:如下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
        <div id="container">
          <tb-heading :level="2">
            <a href="#">Hello world!</a>
          </tb-heading>
        </div>
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('tb-heading', {
          render: function(createElement) {
            return createElement(
              'h' + this.level,    // tag name 标签名称
              this.$slots.default  // 子组件
            )
          },
          props: {
            level: {
              type: Number,
              required: true
            }
          }
        });
        new Vue({
          el: '#container'
        });
      </script>
    </html>

    查看效果

    理解函数式组件

    函数式组件我们标记组件为 functional, 意味着它无状态(没有data), 无实列(没有this上下文)。
    一个函数式组件像下面这样的:

    Vue.component('my-component', {
      functional: true,
      // 为了弥补缺少的实列
      // 提供第二个参数作为上下文
      render: function(createElement, context) {
    
      },
      // Props 可选
      props: {
    
      }
    })

    组件需要的一切通过上下文传递,包括如下:
    props: 提供props对象
    children: VNode子节点的数组
    slots: slots对象
    data: 传递给组件的data对象
    parent: 对父组件的引用
    listeners: (2.3.0+) 一个包含了组件上所注册的 v-on 侦听器的对象。这只是一个指向 data.on 的别名。
    injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。

    在添加 functional: true 之后,组件的 render 函数之间简单更新增加 context 参数,this.$slots.default 更新为 context.children,之后this.level 更新为 context.props.level。
    如下代码演示:

    <!DOCTYPE html>
    <html>
      <head>
        <title>演示Vue</title>
        <style>
          
        </style>
      </head>
      <body>
    
        <div id="container">
          {{msg}}
          <choice>
            <item value="1">test</item>
          </choice>
        </div>
    
      </body>
      <script src="./vue.js"></script>
    
      <script>
        Vue.component('choice', {
          template: '<div><ul><slot></slot></ul></div>'
        });
    
        Vue.component('item', {
          functional: true,
          render: function(h, context) {
            return h('li', {
              on: {
                click: function() {
                  console.log(context);
                  console.log(context.parent);
                  console.log(context.props)
                }
              }
            }, context.children)
          },
          props: ['value']
        })
    
        new Vue({
          el: '#container',
          data: {
            msg: 'hello'
          }
        });
      </script>
    </html>

    查看效果

  • 相关阅读:
    【转载】关于Java String, StringBuilder, StringBuffer, Hashtable, HashMap的面试题
    LeetCode: 【L4】N-Queens 解题报告
    【转载】在美国找工作秘籍
    Lintcode: Kth Largest Element 解题报告
    LeetCode: Reverse Integer 解题报告
    Lintcode: First Bad Version 解题报告
    九章面试题:Find first K frequency numbers 解题报告
    tomcat之虚拟目录
    百度搜索结果如何屏蔽百家号内容
    CentOS7之Rsync+Inotify架构实现实时同步文件和文件夹
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/7528621.html
Copyright © 2011-2022 走看看