“Vue从入门到实战:render函数”的版本间的差异
来自CloudWiki
(创建页面,内容为“Vue推荐在大多数情况下使用模板来构建HTML, 然后在一些场景中,你可能需要Javascript的编程能力,这时可以使用render函数,它…”) |
|||
(未显示同一用户的1个中间版本) | |||
第1行: | 第1行: | ||
+ | ==背景== | ||
Vue推荐在大多数情况下使用模板来构建HTML, | Vue推荐在大多数情况下使用模板来构建HTML, | ||
然后在一些场景中,你可能需要Javascript的编程能力,这时可以使用render函数,它比模板更接近编译器。 | 然后在一些场景中,你可能需要Javascript的编程能力,这时可以使用render函数,它比模板更接近编译器。 | ||
+ | [[文件:vue21022001.png|600px]] | ||
+ | 假设我们要生成一些带锚点的标题: | ||
+ | |||
+ | <nowiki><h1> | ||
+ | <a name="hello-world" href="#hello-world"> | ||
+ | Hello world! | ||
+ | </a> | ||
+ | </h1></nowiki> | ||
+ | |||
+ | 对于上面的 HTML,你决定这样定义组件接口: | ||
+ | |||
+ | <nowiki><anchored-heading :level="1">Hello world!</anchored-heading></nowiki> | ||
+ | |||
+ | 当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时,你可能很快想到这样实现: | ||
+ | |||
+ | <nowiki><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 | ||
+ | } | ||
+ | } | ||
+ | })</nowiki> | ||
+ | |||
+ | 虽然模板在大多数组件中都非常好用,但在本例中不太合适,模板代码冗长,且<slot>元素在每一级标题元素中都重复书写了。 | ||
+ | |||
+ | ==render函数的引入== | ||
+ | 下面改用render函数重复上面的示例,代码精简了很多 | ||
+ | |||
+ | render函数 又称渲染函数,字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。 | ||
<nowiki> | <nowiki> | ||
第16行: | 第69行: | ||
<div id="app"> | <div id="app"> | ||
− | <anchored-heading :level=" | + | |
+ | <anchored-heading :level="3"> | ||
<a name="hello-world" href="#hello-world"> | <a name="hello-world" href="#hello-world"> | ||
Hello world! | Hello world! | ||
</a> | </a> | ||
</anchored-heading> | </anchored-heading> | ||
+ | |||
+ | |||
</div> | </div> | ||
<script type = "text/javascript"> | <script type = "text/javascript"> | ||
第43行: | 第99行: | ||
</body> | </body> | ||
</html></nowiki> | </html></nowiki> | ||
+ | |||
+ | 注:$slots用于以编程方式访问由插槽分发的内容,每个命名的插槽都有其相应的属性。default属性包含了所有未包含在命名插槽中的节点或v-slot:default的内容。 | ||
+ | |||
+ | ==render函数的解释== | ||
+ | ===createElement函数=== | ||
+ | 该函数用于返回描述节点信息及其子节点子信息的一个对象,即虚拟节点(简称为VNode) | ||
+ | |||
+ | 一般带有三个参数: | ||
+ | *第一个参数是要创建的元素节点的名字(字符串形式)或者组件选项(对象形式); | ||
+ | *第二个参数是元素的属性集合(包括普通属性、prop、事件属性、自定义指令等),以对象形式给出 | ||
+ | *第三个参数是子节点的信息,以数组形式给出,如果该元素只有文本子节点,那么直接以字符串形式给出即可,如果还有子元素,则继续调用createElement函数。 | ||
+ | |||
+ | 上述代码改进版,添加了a标签和链接: | ||
+ | |||
+ | <nowiki> | ||
+ | <!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></nowiki> | ||
+ | |||
+ | ===注意:Vnode必须唯一=== | ||
+ | 组件树中的所有 VNode 必须是唯一的。这意味着,下面的渲染函数是不合法的: | ||
+ | |||
+ | render: function (createElement) { | ||
+ | <nowiki>var myParagraphVNode = createElement('p', 'hi') | ||
+ | return createElement('div', [ | ||
+ | // 错误 - 重复的 VNode | ||
+ | myParagraphVNode, myParagraphVNode | ||
+ | ]) | ||
+ | }</nowiki> | ||
+ | |||
+ | 如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落: | ||
+ | |||
+ | |||
+ | <nowiki>render: function (createElement) { | ||
+ | return createElement('div', | ||
+ | Array.apply(null, { length: 20 }).map(function () { | ||
+ | return createElement('p', 'hi') | ||
+ | }) | ||
+ | ) | ||
+ | }</nowiki> |
2021年2月20日 (六) 03:00的最新版本
背景
Vue推荐在大多数情况下使用模板来构建HTML,
然后在一些场景中,你可能需要Javascript的编程能力,这时可以使用render函数,它比模板更接近编译器。
假设我们要生成一些带锚点的标题:
<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') }) ) }