pocket-manual icon indicating copy to clipboard operation
pocket-manual copied to clipboard

Vue 渲染函数

Open FishPlusOrange opened this issue 7 years ago • 0 comments
trafficstars

在 Vue 项目开发过程中,我们多多少少都使用过 Vue 渲染函数,这里对其进行一个简单的总结。

基础应用

个人在编写一些可复用的组件时常常会用到 render 函数。在绝大多数情况下,Vue 推荐使用模版来创建 HTML。然而在一些场景中,我们确实需要 JS 的编程能力,这时就可以用 render 函数,相比模版,它更接近编译器。

使用模板

看一个官方的例子:

<!-- 锚点标题组件 -->
<h1>
  <a name="hello-world" href="#hello-world">Hello world!</a>
</h1>

对于该锚点标题组件,假设我们这样定义组件接口:

<!-- 组件接口定义 -->
<anchored-heading :level="1">Hello world!</anchored-heading>

如果只能通过 prop 动态生成该组件,我们一般这样实现:

<script type="text/x-template" id="anchored-heading-template">
  <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>
</script>
Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

该组件的整体实现比较简单,但是我们可以看到,通过模板实现的代码比较冗长,为了在不同级别的标题中插入锚点元素,我们需要重复地使用插槽。

使用渲染函数

虽然模版在大多数组件中都非常好用,但很明显在这里它并不是这样的。这里我们就可以使用 render 函数重写上面的例子:

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子元素数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

和模板相比,使用渲染函数实现,其代码明显简明很多。

要想用好 render 函数,就必须掌握 createElement 方法的使用。以下详细讲解一下 createElement 参数。

  • 第一个参数 {String | Object | Function}

第一个参数是必需参数,接收一个 HTML 标签字符串、组件选项对象、解析上述任何一种的函数,比如上面例子中的 'h' + this.level

再举一个简单的例子,接收一个 HTML 标签字符串:

Vue.component('example-element', {
  render: function (createElement) {
    return createElement('div')
  }
})

可以改成组件选项对象:

Vue.component('example-element', {
  render: function (createElement) {
    return createElement({
      template: '<div></div>'
    })
  }
})

也可以改成解析上述任何一种的函数:

Vue.component('example-element', {
  render: function (createElement) {
    var getElement = function() {
      return 'div'
    }
    return createElement(getElement())
  }
})

这里 getElement 函数返回了一个 HTML 标签字符串。

  • 第二个参数 {Object}

第二个参数是可选参数,接收一个包含模板相关属性的数据对象,比如 classstyleattrs 等等:

Vue.component('example-element', {
  render: function (createElement) {
    return createElement('div', {
      class: {
        exampleClass: true
      },
      style: {
        color: '#000'
      },
      attrs: {
        id: 'exampleId'
      },
      domProps: {
        innerHTML: 'This is an example.'
      }
    })
  }
})

该参数的详细内容可以查阅官方文档

  • 第三个参数 {String | Array}

第三个参数是可选参数,接收由 createElement 构建而成的子虚拟节点对象数组(VNodes),也可以使用字符串来生成文本虚拟节点。比如:

Vue.component('example-element', {
  render: function (createElement) {
    return createElement('div', {
      class: {
        exampleClass: true
      },
      style: {
        color: '#000'
      },
      attrs: {
        id: 'exampleId'
      },
      domProps: {
        innerHTML: 'This is an example.'
      }
    }, {
      createElement('span', 'This is a child element.')
    })
  }
})

深入理解

接下来深入理解一下 render 函数。

整体流程

先回顾一下 Vue 整体流程:

  • 通过 new Vue 执行初始化
  • 调用 $mount 方法进行挂载
  • 通过 template 生成 render 函数或自定义 render 函数
  • 通过 Watcher 监听数据变化
  • 当数据发生变化时,render 函数被触发生成 VNode
  • 通过 patch 对比新旧 VNode
  • 通过 diff 更新 DOM

在 Vue 中,我们可以通过 template 或者 render 函数告诉 Vue 页面 HTML 组成是什么,Vue 就会自动保持对页面 HTML 的更新。

虚拟 DOM

Vue 2.0在渲染层使用了虚拟 DOM,通过虚拟 DOM 对页面实际 DOM 变化进行追踪。

上面例子中 createElement 返回一个虚拟节点对象,即 VNodeVNode 告诉 Vue 页面 HTML 上所渲染的节点信息,包括其子节点。虚拟 DOM 就是由 Vue 组件树建立起来的整个 VNode 树。

编译过程

在编译过程中,Vue 的编译器把 template 编译成一个 render 函数,当 render 函数被调用时就会结合数据被渲染并返回一个虚拟 DOM 树。大致流程如下:

template => render 函数 => 虚拟 DOM 树 => 真实 DOM

无论是通过 template 还是 render 函数,最终得到的都是 render 函数。只不过直接使用 render 函数的方式省去了编译器 compile 这一步骤。

使用场景

在开发过程中,我们需要根据具体需求具体分析使用哪种方式。其中,template 属于声明式渲染,比较直观,容易理解,适合逻辑简单的需求,缺点是灵活性差;render 函数属于用户自定义,灵活性高,能够胜任复杂的逻辑,缺点是可读性差。

如有理解不当之处,望路过的大神可以帮忙指出。

参考:

FishPlusOrange avatar Jun 26 '18 13:06 FishPlusOrange