- 前言
1.1 HTML DOM树与和Vue的virtual DOM
我们知道,浏览器在解析HTML文件时,会将HTML标签解析成一个DOM树(tree of DOM nodes) 。通过结构化的组织节点元素,浏览器可以很方便的跟踪整个页面的情况,但频繁的局部更新节点代价很高。
为了更高效的渲染HTML,Vue.js和React以及Ember.js一样,根据真实DOM的映射构建对应的JS对象,也就是虚拟DOM(Virtual DOM)。在数据和DOM之间创建一个缓冲地带,不用每次都更新DOM,详情见下面React Virtual DOM的示意图:
React Virtual DOM示意图
1.2 Vue组件中的Slot
Vue.js的Slot机制是指,父组件在嵌套使用子组件时,可以传递HTML代码片段给子组件渲染,但完全不用关心这段HTML应该放在子组件什么位置。
是不是很懵逼,没看懂?举个例子吧:
<div>
<h1>
vue.js Render()函数&JSX
</h1>
<!-- 这里是可能传递的代码片段1 -->
<span><Button>分享到新浪微博</Button></span>
<!-- 这里是可能传递的代码片段2 -->
<span><Button>分享到微信</Button></span>
</div>
我们开始用Vvue.jsSlot机制来实现
定义article.vue组件
<tempalte>
<div>
<h1>
{{title}}
</h1>
<!-- 子组件只关心slot摆放的位置,而不关心传进来的是什么 -->
<slot></slot>
</div>
</tempalte>
<script>
export default{
name : 'Article',
props : ['title']
}
</script>
main.vue使用标题组件
<template>
<div>
<article :title="titleStr">
<span>
<Button v-if="userType === 'weibo'">分享到新浪微博</Button>
<Button v-if="userType === 'weixin'">分享到微信</Button>
</span>
</article>
</div>
</template>
<script>
export default{
name : 'Main',
data() {
return: {
userType: 'weibo',
titleStr: 'vue.js Render()函数&JSX'
}
}
}
</script>
再说slot,顾名思义,Vue.js通过给程序设置一个类似插槽/投币口的机制,实现内容分发:
子组件定义好插槽位置;
父组件可以插入任何,符合Slot规范的代码片段;
如此一来,既实现了程序的解耦,使用又很方便。
当然啦,Vue.js还支持具名插槽,也就是通过定义key名来区分不同的slot代码片段,这里就不展开讨论了。
2. render()函数
2.1 为什么要使用render()函数
使用过Vue.js的朋友都知道,好像大部分时间都在使用template的方式来创建HTML,因为vue提供了v-if、v-for等一系列的控制指令,让我们开发体验轻松又愉悦。
但除此之外,其实Vue.js还提供了render()函数来创建HTML。让我们可以通过JS逻辑代码,更灵活的创建HTML。
如同vue官网的例子所说:在封装文章标题的
<!-- 用template实现 -->
<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
}
}
})
2.2 render()函数的传参
render(crateElmentFn,context){}
createElemntFn是Vue用来动态创建HTML的核心方法【注意:把createElement函数命名为h是vue.js的通用惯例,记得两者是同一个东西】
context是组件实例的上下文环境,包括了组件实例的所有属性
props: 提供props 的对象
children: VNode 子节点的数组
slots: slots 对象
data: 传递给组件的 data 对象
parent: 对父组件的引用
3. createElement()函数
3.1 createElement() 传参
createElemnt(newNode,newNodeConfig,childVNodeList)
createElemnt()函数主要有三个参数:
newNode:要创建的节点【必填参数】
参数类型: {String | Object | Function},可以是要创建的HTML 标签名称,也可以是组件对象,也可以是返回为String或Vue Object的异步函数
newNodeConfig:新节点的配置对象【选填】
childVNodeList:新节点要包含的子节点集合【选填】
参数类型: {String | Array}
注意事项:vue官方教程标明传递的VNodes必须是唯一的,如果想重复创建相同的HTML元素,需要用工厂函数来实现
调用示例:
// @returns {VNode}
createElement(
‘div’,
{},
[
‘Some text comes first.’,
createElement(‘h1’, ‘A headline’),
createElement(MyComponent, {
props: {
someProp: ‘foobar’
}
})
]
)
3.2 newNodeConfig参数详解
{
// 和 `v-bind:class` 的 API 相同【注意:由于是关键字,要用单引号包含】
'class': {
foo: true,
bar: false
},
// 和 `v-bind:style` 的 API 相同【注意:由于是关键字,要用单引号包含】
'style': {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 属性
attrs: {
id: 'foo'
},
// 组件 props
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件处理程序嵌套在 `on` 字段下,
// 然而不支持在 `v-on:keyup.enter` 中的修饰符。
// 因此,你必须手动检查
// 处理函数中的 keyCode 值是否为 enter 键值。
on: {
click: this.clickHandler
},
// 仅对于组件,
// 用于监听原生事件,而不是组件内部
// 使用 `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。
// 注意,由于 Vue 会追踪旧值,
// 所以不能对`绑定`的`旧值`设值
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽(scoped slot)的格式如下
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果此组件是另一个组件的子组件,
// 需要为插槽(slot)指定名称
slot: 'name-of-slot',
// 其他特殊顶层(top-level)属性
key: 'myKey',
ref: 'myRef'
}
3.3 使用createElement()替代template创建HTML
3.3.1 条件判断和循环渲染
template代码:
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
render代码:
props: ['items'],
render: function (createElement) {
// 使用原生JS代码来做条件判断
if (this.items.length) {
// 使用map()来循环调用createElement()函数
return createElement('ul', this.items.map(function (item) {
return createElement('li', item.name)
}))
} else {
return createElement('p', 'No items found.')
}
}
3.3.2 数据绑定
render代码:
props: [‘value’],
render: function (createElement) {
// 【注意:由于JS可以用function作为函数的参数传递,为了避免this指针的混乱,在render函数里,要记得对this指针进行缓存】
var self = this
return createElement(‘input’, {
// 手动vue emit事件的触发,来手动控制value值的维护,这是深入底层实现需要付出的代价
domProps: {
value: self.value
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
}
3.3.3 事件绑定
Vue.js在createElemnt()函数中,映射了一系列事件指令修饰符的前缀:
修饰符 前缀
.passive &
.capture !
.once ~
.capture.once 或 .once.capture ~!
示例:
createElement('input',{
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
})
3.3.4 slot传递
如果像前文所述的,父组件在调用子组件的时候,想传递Slot,可以通过this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …nt('div', this.slots.default)
}
如果在传递Slot时要传参,则可通过this.$scopedSlots
props: ['message'],
render: function (createElement) {
// 等同于`<div><slot :text="message"></slot></div>`
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
- JSX
虽然render()函数创建HTML代码片段很灵活,但整段整段的JS配置代码,的确阅读性很差。为了让代码更简单,我们可以使用JSX,直接在JS代码中书写HTML:
render: function (h) {
return (
Hello world!