1 引子
锚点是网页中超级链接的一种,又叫命名锚记。命名锚记像一个迅速定位器一样是一种页面内的超级链接,运用相当普遍。它的英文名是 anchor。
使用命名锚记可以在文档中设置标记,这些标记通常放在文档的特定主题处或顶部。然后可以创建到这些命名锚记的链接,这些链接可快速将访问者带到指定位置。
如果把这个功能封装为组件,它一般是这样写的:
html:
研究人员发现,距离我们6光年远的一颗巨大的“超级地球”行星可能存在简单生命。Barnard b(或GJ 699 b)是最近发现的一颗围绕巴纳德星(是距离地球第二近的恒星)运行的“超级地球”行星。Barnard b被认为极度寒冷,温度与木卫二相似,约为摄氏零下150度。复制代码6光年远“超级地球”或存在生命
js:
Vue.component('anchor', { template: '#anchor', props: { level: { type: Number, required: true }, title: { type: String, default: '' } }});var app = new Vue({ el: '#app', data: {}});复制代码
当点击锚点 “6光年远“超级地球”或存在生命” 时,会跳到当前页所指定的内容。锚点粗细根据 level 值来决定。
这样写的缺点是:组件的 template 代码冗长,只有少部分不同,其它大部分都是相同的。
我们可以使用 Render 函数,通过拼接字符串的形式来构造 <h>
元素。
html:
复制代码6光年远“超级地球”或存在生命
js:
Vue.component('anchor2', { props: { level: { type: Number, required: true }, title: { type: String, default: '' } }, render: function (createElement) { return createElement( 'h' + this.level, [ createElement( 'a', { domProps: { href: '#' + this.title } }, this.$slots.default ) ] ) }});var app2 = new Vue({ el: '#app2', data: {}});复制代码
效果相同,但这里的场景,使用 Render 函数明显简化了代码。
以上示例
2 createElement
我们使用 createElement 来构建 Vue.js 的 Virtual Dom 模板。
2.1 参数
createElement 有三个参数:
参数 | 是否必选 | 说明 |
---|---|---|
HTML 标签、组件选项或函数 | 必选 | - |
数据对象 | 可选 | 在 template 中使用。 |
子节点 | 可选 | 可为 String 或 Array。 |
上述示例中,加上注释,我们就会更清楚一些:
return createElement( // HTML 标签、组件选项或函数(String|Object|Function) 'h' + this.level, //对应属性的数据对象(可选) {}, //子节点,可为 String 或 Array [ createElement( 'a', { domProps: { href: '#' + this.title } }, this.$slots.default ) ])复制代码
其中的数据对象是这样的结构:
//对应属性的数据对象(可选){ //对应 v-bind:class 'class': {}, //对应 v-bind:style style: {}, //HTML 属性 attrs: {}, //组件 props props: {}, //DOM 属性 domProps: {}, //自定义 on 事件监听器,不支持修饰器 on: {}, //仅适用于组件,用于监听原生事件 nativeOn: {}, //自定义指令 directives: [], //作用域 slot // {name:props => VNode | Array} scopedSlots: {}, //子组件中的 slot 名称(如果组件中有定义) slot: 'xxx', //其它自定义属性 xxx: 'xxx'}复制代码
在此之前,我们在 template 中都是在组件标签中使用 v-bind:class
等指令,而在 Render 函数中,这些都定义在数据对象中咯。
虽然 Render 函数灵活,但在某些场景下却显得臃肿。让我们来看一个例子,假设我们需要定义一个绑定了 class 以及 click 的简单 div 组件,如果用 template 方式,是这样编码的:
html:
复制代码
js:
Vue.component('e', { template: '\后挡风玻璃上的细线竟有如此妙用\ ', data: function () { return { isShow: true } }, methods: { click: function () { console.log('点击'); } }})var app = new Vue({ el: '#app', data: {}});复制代码
css:
.show { cursor: pointer}复制代码
如果使用 Render 函数,那么编码方式是这样的:
Vue.component('e2',{ render:function (createElement) { return createElement( 'div', { class:{ 'show':this.isShow }, attrs:{ id:'e2' }, on:{ click:this.click } }, '后挡风玻璃上的细线竟有如此妙用' ) }, ...});复制代码
所以,在这个场景中,使用 template 的编码方式更简洁。
2.2 限制
组件树中,如果 VNode 是组件或者是含有组件的 slot,那么 VNode 必须唯一。
假设我们希望在子节点内渲染出两个子组件。
2.2.1 错误示例 1 —— 重复使用组件
html:
复制代码
js:
var Child3 = { render: function (createElement) { return createElement('p', '《怪物猎人》系列世界观设定科普'); }};Vue.component('e3',{ render: function (createElement) { //使用组件 Child3 来创建子节点 var ChildNode= createElement(Child3); return createElement('div',[ ChildNode,ChildNode ]) }});var app3 = new Vue({ el: '#app3', data: {}});复制代码
渲染结果:
复制代码《怪物猎人》系列世界观设定科普
因为受到限制,所以实际只渲染出一个子组件!
2.2.2 错误示例 2 —— 重复使用组件的 slot
html:
复制代码
js:
//全局注册组件Vue.component('Child4', { render: function (createElement) { return createElement('p', '高端商务本存在必要性解读:解决用户痛点更专业'); }});Vue.component('e4', { render: function (createElement) { return createElement('div', [ this.$slots.default, this.$slots.default ]) }});var app4 = new Vue({ el: '#app4', data: {}});复制代码
渲染结果:
复制代码高端商务本存在必要性解读:解决用户痛点更专业
也是因为受到限制,所以实际只渲染出一个子组件!
有以下方法可以渲染出多个组件——
2.2.3 循环与工厂函数
html:
复制代码
js:
var Child5 = { render: function (createElement) { return createElement('p', '智能科技如何助力实体经济和大众创业?'); }};Vue.component('e5', { render: function (createElement) { return createElement('div', [ Array.apply(null, { length: 3 }).map(function () { return createElement(Child5); }) ]) }});var app5 = new Vue({ el: '#app5', data: {}});复制代码
这里通过 apply 工厂函数设定了一个长度为 3 的数组,并通过 map 函数,对数组的每一项创建出子组件。
相关知识点:
- 每个函数都包含两个非继承来的的方法:apply() 和 call(),它们都用于在特定的作用域下调用函数,实际上等于设置函数体内 this 对象的值。apply() 接收两个参数(运行函数的作用域、参数数组),其中的参数数组可以是 Array 实例,也可以是 arguments 对象。
- map() 是迭代方法,它对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组。
效果:
2.2.4 深度拷贝 slot
html:
复制代码
js:
Vue.component('Child6', { render: function (createElement) { return createElement('p', '线上教育发展将呈现四大趋势'); }});Vue.component('e6', { render: function (createElement) { //拷贝 slot 节点 function copy(vnode) { //递归遍历所有子节点,并拷贝 const children = vnode.children && vnode.children.map(function (vnode) { return copy(vnode); }); const element = createElement( vnode.tag, vnode.data, children ); element.text = vnode.text; element.isComment = vnode.isComment; element.componentOptions = vnode.componentOptions; element.elm = vnode.elm; element.context = vnode.context; element.ns = vnode.ns; element.isStatic = vnode.isStatic; element.key = vnode.key; return element; } const vNodes = this.$slots.default; const copyVNodes1 = vNodes.map(function (vnode) { return copy(vnode); }); const copyVNodes2 = vNodes.map(function (vnode) { return copy(vnode); }); return createElement('div', [ vNodes, copyVNodes1, copyVNodes2 ]) }});var app6 = new Vue({ el: '#app6', data: {}});复制代码
在 Render 函数中,我们创建了一个拷贝 slot 节点的工厂函数,通过递归将 slot 中的所有子节点都做了拷贝,同时还复制了 vnode 中的关键属性。这种手法主要运用于独立组件的开发。
效果: