Vue从入门到实战:render函数

来自CloudWiki
跳转至: 导航搜索

背景

Vue推荐在大多数情况下使用模板来构建HTML,

然后在一些场景中,你可能需要Javascript的编程能力,这时可以使用render函数,它比模板更接近编译器。

Vue21022001.png

假设我们要生成一些带锚点的标题:

<h1>
  <a name="hello-world" href="#hello-world">
    Hello world!
  </a>
</h1>

对于上面的 HTML,你决定这样定义组件接口:

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

当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时,你可能很快想到这样实现:

<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
    }
  }
})

虽然模板在大多数组件中都非常好用,但在本例中不太合适,模板代码冗长,且<slot>元素在每一级标题元素中都重复书写了。

render函数的引入

下面改用render函数重复上面的示例,代码精简了很多

render函数 又称渲染函数,字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<script src="vue.js"></script>
	</head>
	<body>
		
		<div id="app">

			<anchored-heading :level="3">
				<a name="hello-world" href="#hello-world">
    			Hello world!
  			</a>
			</anchored-heading>


		</div>
		<script type = "text/javascript">
			Vue.component('anchored-heading', {
			  render: function (createElement) {
			    return createElement(
			      'h' + this.level,   // 标签名称
			      this.$slots.default // 子节点数组
			    )
			  },
			  props: {
			    level: {
			      type: Number,
			      required: true
			    }
			  }
			})
			new Vue({
			  el: '#app'
			})
		</script>
	</body>
</html>

注:$slots用于以编程方式访问由插槽分发的内容,每个命名的插槽都有其相应的属性。default属性包含了所有未包含在命名插槽中的节点或v-slot:default的内容。

render函数的解释

createElement函数

该函数用于返回描述节点信息及其子节点子信息的一个对象,即虚拟节点(简称为VNode)

一般带有三个参数:

  • 第一个参数是要创建的元素节点的名字(字符串形式)或者组件选项(对象形式);
  • 第二个参数是元素的属性集合(包括普通属性、prop、事件属性、自定义指令等),以对象形式给出
  • 第三个参数是子节点的信息,以数组形式给出,如果该元素只有文本子节点,那么直接以字符串形式给出即可,如果还有子元素,则继续调用createElement函数。

上述代码改进版,添加了a标签和链接:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<script src="vue.js"></script>
	</head>
	<body>
		
		<div id="app">
			<anchored-heading :level="3">
				Hello world!
			</anchored-heading>
		</div>
		<script type = "text/javascript">
			var getChildrenTextContent = function (children) {
			  return children.map(function (node) {
			    return node.children
			      ? getChildrenTextContent(node.children)
			      : node.text
			  }).join('')
			}

			Vue.component('anchored-heading', {
			  render: function (createElement) {
			    // 创建 kebab-case 风格的 ID
			    var headingId = getChildrenTextContent(this.$slots.default)
			      .toLowerCase()
			      .replace(/\W+/g, '-')
			      .replace(/(^-|-$)/g, '')

			    return createElement(
			      'h' + this.level,
			      [
			        createElement('a', {
			          attrs: {
			            name: headingId,
			            href: '#' + headingId
			          }
			        }, this.$slots.default)
			      ]
			    )
			  },
			  props: {
			    level: {
			      type: Number,
			      required: true
			    }
			  }
			})
			new Vue({
			  el: '#app'
			})
		</script>
	</body>
</html>

注意:Vnode必须唯一

组件树中的所有 VNode 必须是唯一的。这意味着,下面的渲染函数是不合法的:

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

如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:


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