vue越來越熟練,但是一些基礎的概念卻漸漸模糊。模糊中又會變得自我懷疑,然後重新梳理,重新認識、深入了解 ~ VNode、elm、context、el ~他們是個啥,擔任着什麼樣子的角色 ?那就得從指令鈎子函數開始
研究準備階段
vnode源碼位址
/* @flow */
export default class VNode {
tag: string | void; /** 目前标簽如:div */
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void; /** 存在于VNode中 即div.class名所代表一個Node對象,可直接操作dom */
ns: string | void;
context: Component | void; /** 上下文環境,即目前VNode所在的上下文環境。也即是目前VNode的父虛拟節點上下文環境 */
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
/*目前節點的标簽名*/
this.tag = tag
/*目前節點對應的對象,包含了具體的一些資料資訊,是一個VNodeData類型,可以參考VNodeData類型中的資料資訊*/
this.data = data
/*目前節點的子節點,是一個數組*/
this.children = children
/*目前節點的文本*/
this.text = text
/*目前虛拟節點對應的真實dom節點*/
this.elm = elm
/*目前節點的命名空間*/
this.ns = undefined
/*上下文作用域:VueComponent 即我們知道的 this */
this.context = context
/*函數化元件作用域*/
this.functionalContext = undefined
/*節點的key屬性,被當作節點的标志,用以優化*/
this.key = data && data.key
/*元件的option選項*/
this.componentOptions = componentOptions
/*目前節點對應的元件的執行個體*/
this.componentInstance = undefined
/*目前節點的父節點*/
this.parent = undefined
/*簡而言之就是是否為原生HTML或隻是普通文本,innerHTML的時候為true,textContent的時候為false*/
this.raw = false
/*靜态節點标志*/
this.isStatic = false
/*是否作為根節點插入*/
this.isRootInsert = true
/*是否為注釋節點*/
this.isComment = false
/*是否為克隆節點*/
this.isCloned = false
/*是否有v-once指令*/
this.isOnce = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}
// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.asyncMeta = vnode.asyncMeta
cloned.isCloned = true
return cloned
}
上面則展示了VNode源碼,且标注了部分注釋。但即使通讀了上面源碼,也隻是模模糊糊、并不深刻。因為隻有放入項目即使用,才能身臨其境的深刻了解。
接下來 , 将對
VNode、elm、context、el
相關程式檔案放入項目中,進行窺探 ~
将
element-ui
源碼中的一個js檔案
./package/src/utils/clickoutside.js
放到Vue項目中,并在main.js中進行全局引入
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Clickoutside from '@/views/clickoutside';
Vue.use(Clickoutside)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在引入的clickoutside.js檔案中,對關鍵位置作了日志log的列印 :
/**
* package/src/utils/clickoutside.js
* v-clickoutside
* @desc 點選元素外面才會觸發的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose">
* ```
*/
const clickoutsideContext = '@@clickoutsideContext';
export default {
bind(el, binding, vnode) {
const documentHandler = function(e) {
console.log('directives vnode =>', vnode)
console.log('directives vnode.context =>', vnode.context)
console.log('directives e.target =>', e.target)
console.log('directives vnode.context[el[clickoutsideContext].methodName] =>', vnode.context[el[clickoutsideContext].methodName])
if (vnode.context && !el.contains(e.target)) {
vnode.context[el[clickoutsideContext].methodName]();
}
};
console.log('directives el是什麼類型 =>', (typeof el))
el[clickoutsideContext] = {
documentHandler,
methodName: binding.expression,
arg: binding.arg || 'click'
};
console.log('directives el =>', el)
console.log('directives el[clickoutsideContext].methodName =>', el[clickoutsideContext].methodName)
console.log('directives el[clickoutsideContext].arg =>', el[clickoutsideContext].arg)
console.log('directives el[clickoutsideContext] =>', el[clickoutsideContext])
document.addEventListener(el[clickoutsideContext].arg, documentHandler);
},
update(el, binding) {
el[clickoutsideContext].methodName = binding.expression;
},
unbind(el) {
document.removeEventListener(
el[clickoutsideContext].arg,
el[clickoutsideContext].documentHandler);
},
install(Vue) {
Vue.directive('clickoutside', {
bind: this.bind,
unbind: this.unbind
});
}
};
項目中使用看代碼
<templete/>
<template >
<div class="acc-transfer-container" ref="template">
<div class="header">
<div class="img-back">
<span class="img"></span>
</div>
<div class="span">
<span>賬戶轉賬c</span>
</div>
</div>
<div class="transfer-list">
<li-acc-click mType="收款賬戶" :account="payee" ></li-acc-click>
<li-show mType="币種" :allBalance="currency" ></li-show>
<li-acc-input :callbackInput="callbackInput"></li-acc-input>
<li-acc-click mType="付款賬戶" :account="payAcc"></li-acc-click>
<li-show :allBalance="allBalance"></li-show>
</div>
<!-- 引入自定義指令 v-clickoutside ->
<div class="outside-click" v-clickoutside="clickoutsideMehthod">點選 有 觸動</div>
<el-button type="danger" round size="medium" class="btn_next" @click="sayHello($event)">下一步</el-button>
</div>
</template>
從代碼中看,下截圖中的按鈕
點選 有 觸動
則是引入自定義指令的按鈕
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9UFRNl3Yq1ENJRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLyMTN0UTM0ETM4EzNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
以上是為研究 VNode、elm、context、el的準備過程,ok!接下來進入日志的研究過程。
研究開始階段
注意: 這些
VNode、elm、context、el
的研究是出自指令鈎子函數。即從指令函數參數vnode 和 el出發,對自定義指令所用到的标簽,進行研究。一定要明确這裡介紹和研究的基礎環境條件。
研究從指令鈎子函數出發,研究第一項 el
el
經過上述準備,執行運作該vue項目工程,首次進入該vue頁面時,有日志 輸出。
研究從指令鈎子函數出發,在vue官網對指令鈎子函數的參數,給以解釋
結合日志輸出截圖、clickoutside.js以及vue程式,來看
指令鈎子函數中的參數el 是一個object的對象,且該對象
指向(僅指向)
<div class="outside-click">點選 有 觸動</div>
所代表的對象。vue官網說el是指令所綁定元素,可直接用來操作dom,顯然跟
<templete>
中的
不期而遇。
提問:這裡是對指令函數參數中el的解釋,那麼與VNode中的elm有什麼差別?與VNode中的context中的el又有什麼差別? 帶着問題接着向下看呗 ~
研究從指令鈎子函數出發,研究第二項 vnode
vnode
點選使用指令
v-clickoutside
的标簽按鈕"點選有觸動"外邊,有日志 輸出
根據該日志輸出來看VNode,該VNode是來自指令鈎子函數參數
bind(el, binding, vnode)
即VNode表示
<div class="outside-click" v-clickoutside="clickoutsideMehthod">點選 有 觸動</div>
的虛拟節點
接下來對VNode截圖内的關鍵字段進行逐個檢視、解釋
elm:[div.outside-click] = 是VNode中的一個字段,但是可以看到它的類型是
div.outside-click
對象。可直接用來操作dom,指令函數參數中el和vnode中的elm是同一個!! 為證明這個結論,在bind指令鈎子函數中新增了日志輸出 ,
最終的驗證輸出結果是,符合預期
/** console.log('directives vnode.elm == el =>', vnode.elm ===el, vnode.elm == el) */
directives vnode.elm == el => true true
children:[VNode] = 是VNode中的一個字段,類型是VNode,是
<div class="outside-click" v-clickoutside="clickoutsideMehthod">點選 有 觸動</div>
的數組子節點
進入該數組子節點詳情看,該數組中隻有一個元素,VNode虛拟節點。對應的标簽對象類型是
text
,内容是
點選 有 觸動
。
data:{directives:Array(1), staticClass: “outside-click”} = 是VNode中的一個字段,類型是對象。
tag:“div” = 是VNode中的一個字段,類型是div字元串。
context:VueComponent = 是VNode中的一個字段,類型是VueComponent,是目前虛拟節點所在的上下文環境。
從該context内容詳情中我們是不是看到了熟悉的内容。
研究VNode中的context
從截圖中context:VueComponent,是
$el:div.acc-transfer-container
,說明該上下文環境是使用了自定義指令
v-clickoutside
的标簽,所在的上下文環境。即
<template >
中的
<div class="acc-transfer-container" ref="template">
所創造的環境!創造的-環境!
從該context内容詳情中我們是不是看到了熟悉的内容。哪裡熟悉?即我們在vue開發中,經常在下截圖中使用
this.$el、this.$data、this.$refs
等,還記得不?
說明了什麼?說明我們上截圖中的上下文環境context即我們使用的this,this指向context。而context上下文環境的類型也是VueComponent。明白了吧?!
[
注意:目前結論是以目前案例的層次結構為例子來說明
]
從這個模闆嵌套層次看,
<.script>
中使用的this就是上下文環境,且上下文環境類型是VueComponent。
context中的
$el
:是 指建立目前上下文環境的标簽對象。與指令函數中的el和vnode中的
$elm
不同。context中的
$el
是在指令函數中
el
和vnode中的
$elm
的更外層标簽。而指令函數中的el和vnode中的
$elm
是相同的。
讀懂了以上内容就能清楚以上兩個問題了
1,VNode、elm、context、el ~ 他們是個啥,擔任着什麼樣子的角色 ?
2,指令函數參數中el的解釋,那麼與VNode中的elm有什麼差別?與VNode中的context中的el又有什麼差別?
參考文獻:
https://github.com/answershuto/learnVue/blob/master/docs/VNode
https://cn.vuejs.org