首页 > 编程笔记

Vue render渲染函数和JSX的用法

通常情况下,Vue.js 推荐使用 template 定义视图内容(html),然而有时使用渲染函数生成视图内容要方便得多。

用 template 定义一个子组件,用h n显示插槽里面的内容,代码如下:
<!-- 定义 Vue.js 的视图 -->
<div id="app">
  <sub-component :level="1"> Hello </sub-component>
  <sub-component :level="2"> Hello </sub-component>
  <sub-component :level="4"> Hello </sub-component>
</div>
<script type="text/JavaScript">
    const SubComponent = {
      // template定义视图
      template: '<div>
        <h1 v-if="level === 1"><slot></slot></h1>
        <h2 v-else-if="level === 2"><slot></slot></h2>
        <h3 v-else><slot></slot></h3>
        </div>',
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    };

    // 创建 Vue.js 对象
    const vm = new Vue({
      el: "#app",
      components: {
        SubComponent
      }
    });
</script>
在 template 里面,用 v-if 根据 level 的不同值,渲染不同的 h,如果代码量太大,则可改成渲染函数生成子组件的 html,代码如下:
<!-- 定义 Vue.js 的视图 -->
<div id="app">
  <sub-component :level="1"> Hello </sub-component>
  <sub-component :level="2"> Hello </sub-component>
  <sub-component :level="4"> Hello </sub-component>
</div>
<script type="text/JavaScript">
    const SubComponent = {
      // 用render()函数渲染视图的HTML
      render: function(createElement) {
        return createElement(
          'h' + this.level,
          this.$slots.default   
          )
        },
      props: {
        level: {
            type: Number,
            required: true
        }
      }
    }
    // 创建Vue.js对象
    const vm = new Vue({
        el: "#app",
        components: {
            SubComponent
        }
    })
</script>
这种情况下,渲染函数的使用要比 template 实现方便很多。

虚拟DOM

浏览器接收到 html 页面后,会先将页面内容解析成树形结构,保存在内存中,再显示到页面上。每个元素是一个独立的节点对象,文本也是。

在上面的案例中,createElement('h'+this.level,this.$slots.default)执行的结果,也是创建一个 h 的节点对象,只是这个对象不是原汁原味的 DOM 节点,而是 Vue.js 中的节点描述对象,包含所有描述信息,以便 Vue.js 将其渲染到页面。这种节点,通常称为虚拟节点(Virtual Node),简称为 VNode。虚拟 DOM 是在 Vue.js 里面对所有 Vue.js 组件数的总称。

createElement参数

createElement 的语法是:
createElement({String|Object|function},{Object},{String, Array})
带 3 个参数:
关于第 2 个参数,经常又称为数据对象,里面包含的是组件属性对象对应的数据,代码如下:
// 与 v-bind:class 的 API 相同
// 接收一个字符串、对象或字符串和对象组成的数组
'class': {
  foo: true,
  bar: false
},

// 与 v-bind:style 的 API 相同
// 接收一个字符串、对象, 或对象组成的数组
style: {
  color: 'red',
  fontSize: '14px'
},

// 普通的 HTML attribute
attrs: {
  id: "foo"
},

// 组件prop
props: {
  myProp: 'bar'
},

// DOM property
domProps: {
  innerHTML: 'baz'
},

// 事件监听器在 on 内,
// 但不再支持如 v-on:keyup.enter 这样的修饰器
// 需要在处理函数中手动检查 keyCode
on: {
  click: this.clickHandler
},

// 仅用于组件,用于监听原生事件,而不是在组件内部使用
// vm.$emit 触发的事件
nativeOn: { 
  click: this.nativeClickHandler
},
// 自定义指令. 注意,无法对 binding 中的 oldValue 赋值
// 因为 Vue.js 已经自动进行了同步
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'
// 其他特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果在渲染函数中给多个元素应用了相同的 ref 名
// 则 $refs.myRef 会变成一个数组
refInFor: true
}

特别要注意的是,在 Vue.js 组件树中,VNode 是唯一的。如下代码是不正确的,因为在渲染出来的对象里面有两个一样的 VNode 对象,代码如下:
render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div',
    // 错误,重复的VNode
    myParagraphVNode, myParagraphVNode
  )
}

推荐阅读