vue进阶篇- Render函数
一、什么是Virtual Dom
React和Vue2都使用了Virtual Dom技术,Virtual Dom并不是真正意义上的Dom,而是一个轻量级的JavaScript对象,在状态发生变化时,Virtual Dom会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。
与DOM操作相比,Virtual Dom是基于JavaScript计算的,所以开销会小很多。
正常的DOM节点在HTML中是这样的:
<div id="main">
<p>文本内容</p>
<p>文本内容</p>
</div>
用Virtual Dom 创建的JavaScript 对象一般会是这样:
var vNode ={
tag:'div',
attributes:{
id:'main'
},
children:{
//p节点
}
}
vNode对象通过一些特定的选项描述了真实的DOM结构。
在Vue.js2中,Virtual Dom就是通过一种VNode类表达的,每个DOM元素或组件都对应一个VNode对象.在Vue.js 远吗中是这样定义:
export interface Vnode{
tag ?: string;
data? :vNodeData;
children?: VNode[];
text?: string;
elm?:Node;
ns?:string;
context?:Vue
key?:string |number;
componentOptIons ?: VNodeComponentOptions;
componentInstance?: Vue;
parent?: Vnode;
raw?: boolean;
isRootInsert :boolean;
isComment: boolean;
}
具体包含如下:
- tag 当前节点的标签名。
- data 当前节点的数据对象。
VNodeData代码如下:
export interface VNodeData {
key?: string | number;
slot?: string;
scopedSlots?: { [key: string]: ScopedSlot };
ref?: string;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: { [key: string]: any };
style?: Object[] | Object;
props?: { [key: string]: any };
attrs?: { [key: string]: any };
domProps?: { [key: string]: any };
hook?: { [key: string]: Function };
on?: { [key: string]: Function | Function[] };
nativeOn?: { [key: string]: Function | Function[] };
transition?: Object;
show?: boolean;
inlineTemplate?: {
render: Function;
staticRenderFns: Function[];
};
directives?: VNodeDirective[];
keepAlive?: boolean;
}
-
children
子节点,数组,也是VNode
类型。 -
text
当前节点的文本,一般文本节点或注释节点会有该属性。 -
elm
当前虚拟节点对应的真实的DOM节点。 -
ns
节点的namespace
。 -
context
编译作用域。 -
functionalContext
函数化组件的作用域。 -
key
节点的key
属性,用于作为节点的标识,有利于patch
的优化。 -
componentOptions
创建组件实例时会用到的信息选项。 -
child
当前节点对应的组件实例。 -
parent
组件的占位节点。 -
raw
原始html。 -
isStatic
静态节点的标识。 -
isRootInsert
是否作为根节点插入,被<transition>
包裹的节点,该属性的值为false
。 -
isComment
当前节点是否是注释节点。 -
isCloned
当前节点是否为克隆节点。 -
isOnce
当前节点是否有v-once
指令。
VNode
主要可以分为以下几类:
-
EmptyVNode
没有内容的注释节点 -
ComponentVNode
组件节点 -
TextVNode
文本节点 -
ElementVNode
普通元素节点 -
CloneVNode
克隆节点,可以是以上任意类型的节点,唯一的区别在于isCloned
属性为true
使用Virtual Dom
就可以完全发挥JavaScript的能力。在多数场景下,我们使用template
就足够了,但在一些特定的场景下,使用Virtual Dom
会更简单,下节我们来介绍Vue的Render
函数的用法。
二、什么是render函数
Render函数通过createElement参数来创建Virtual Dom,结构精简,代码少且清晰,这里使用了一个demo实例来说明,我未对实例进行摘抄,我们只有清楚Render函数所有神奇的地方都在这个createElement里就可以了,我们在下一节来详细介绍它的详细配置和用法。
<div id="app">
<anchor :level="2" title="特性">特性</anchor>
</div>
<script>
Vue.component('anchor', {
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 app = new Vue({
el: '#app'
})
</script>
三、createElement用法
1、基本参数createElement
构成了Vue Virtual Dom
的模板,它有3个参数:
createElement(
// {String | Object | Function}
// 一个HTML标签,组件选项,或一个函数
// 必须return上述其中一个
'div',
// {Object}
// 一个对应属性的数据对象,可选
// 可以在template中使用
{
// 稍后详细介绍
},
// {String | Array}
// 子节点(VNodes),可选
[
createElement('h1', 'hello world'),
createElement(MyComponent, {
props: {
someProp: 'foo'
}
}),
'bar'
]
)
第一个参数必选,可以是一个HTML标签,也可以是一个组件或函数;第二个是可选参数,数据对象,在template
中使用。第三个是子节点,也是可选参数,用法一致。
对于第二个参数“数据对象”。具体的选项如下:
{
//和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
on: {
click: this.clickHandler
},
//进对于组件,用于监听原生事件
//而不是组件使用vm.$emit触发的自定义事件
nativeOn: {
click: this.nativeClickHandler
},
//自定义指令
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1+1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
//作用域slot
//{name: props => VNode | Array<Vnode> }
scopedSlots: {
default: props => h('span', props.text)
},
//如果子组件有定义slot的名称
slot: 'name-of-slot',
//其他特殊顶层属性
key: 'myKey',
ref: 'myRef'
}
以往在template里,我们都是在组件的标签上使用形容 v-bind:class、v-bind:style、v-on:click 这样的指令,在Render 函数都将其写在了数据对象里,传统的template 写法是:
<div id="app">
<ele></ele>
</div>
vue.component('ele',{
template:'\
<div id="element" \
:class="{show: show}" \
@click= "handleClick"> 文本内容</div>,
data:function() {
return{
show: true
},
methods:{
handleClick:function(){
console.log('clicked');
}
}
});
var app = new vue({
el: '#app'
})
使用Render 改写:
<div id="app">
<ele></ele>
</div>
Vue.component('ele',{
render: function (createElement){
return createElement{
'div',
{
//动态绑定class,同 :class
class:{
'show':this.show
},
//普通html 特性
attrs:{
id:'element'
},
//给div 绑定click 时间
on:{
click: this .handleClick
}
},
'文本内容'
}
},
data: function(){
return{
show:true
}
},
methods:{
handleClick:function(){
console.log('clicked!');
}
}
});
var app = new Vue({
el: '#app'
})
对比下 template的写法比Render 写法要可读而且简洁。