一、什麼是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 寫法要可讀而且簡潔。