v-text、v-html、v-cloak、v-pre.md
本篇文章,我們簡單的介紹幾個Vue
內置指令的實現。
v-text
v-text
的用法很簡單,以下兩個表達式的作用相同。
<span v-text="msg"></span>
<span>{{msg}}</span>
和所有普通指令一樣,在生成ast
時,v-text
會被解析到el.directives
中。
但在生成render
函數的過程中,在解析屬性時,
我們會首先解析指令。genDirectives
方法中有如下一段代碼:
const gen: DirectiveFunction = platformDirectives[dir.name] || baseDirectives[dir.name]
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, warn)
}
if (needRuntime) {
hasRuntime = true
res += `...`
}
directives概述中我們也提到過,platformDirectives
和baseDirectives
會對部分內置指令進行處理。v-text
就是其中之一。
最終的gen
函數如下所示:
export default function text (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, ‘textContent‘, `_s(${dir.value})`)
}
}
該函數返回的是undefined
,所以needRuntime
最終是false
,所以該結果不會添加到res
上。
addProp
export function addProp (el: ASTElement, name: string, value: string) {
(el.props || (el.props = [])).push({ name, value })
}
它會給el.props
數組中添加一個對象,對象裏保存的name
和value
。
// DOM props
if (el.props) {
data += `domProps:{${genProps(el.props)}},`
}
最終,會添加到domProps
對應的數組中。上面例子中的span
,最終生成的render
函數如下:
_c(‘span‘,{domProps:{"textContent":_s(msg)}})
在patch
過程中的處理,和其他data
中的數據一樣,是通過鉤子函數處理的。
function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (!oldVnode.data.domProps && !vnode.data.domProps) {
return
}
let key, cur
const elm: any = vnode.elm
const oldProps = oldVnode.data.domProps || {}
let props = vnode.data.domProps || {}
// clone observed objects, as the user probably wants to mutate it
if (props.__ob__) {
props = vnode.data.domProps = extend({}, props)
}
for (key in oldProps) {
if (props[key] == null) {
elm[key] = ‘‘
}
}
for (key in props) {
cur = props[key]
if (key === ‘textContent‘ || key === ‘innerHTML‘) {
if (vnode.children) vnode.children.length = 0
if (cur === oldProps[key]) continue
}
if (key === ‘value‘) {
// store value as _value as well since
// non-string values will be stringified
elm._value = cur
// avoid resetting cursor position when value is the same
const strCur = cur == null ? ‘‘ : String(cur)
if (shouldUpdateValue(elm, vnode, strCur)) {
elm.value = strCur
}
} else {
elm[key] = cur
}
}
}
首先會重置oldProps
中props
上不存在的屬性。然後遍歷props
中的屬性,如果key
值textContent
或innerHTML
,則清除children
的內容。
如果key === ‘value‘
這裏應該對input
、select
等標簽的特殊處理。否則,直接設置elm.textContent = cur
,以此來改變文本內容。
v-html
v-html
和v-text
的用法和處理流程基本完全一樣,唯一的區別就是最終v-html
設置的elm.innerHTML = cur
。
用法示例如下:
<span v-html="msg"></span>
v-cloak
這個指令用的比較少,不懂的人看完官方文檔的說明可能還是稀裏糊塗的。它的ast
生成和上面講的普通指令一樣,在genDirectives
時,baseDirectives
中包含了cloak
,但最終返回的gen
是一個空函數。最終它也不會添加到directives
數組中,之後也就沒有了對它的處理。
因為我們的模板再編譯的過程中,頁面中是會顯示Mustache
標簽的。該指令就是在模板編譯之後,被刪除。我們可以添加[v-cloak] { display: none }
,來防止用戶感知到Mustache
標簽 出現。
v-pre
v-pre
表示該會跳過該標簽及其子元素的編譯。
在編譯模板時的start
回調函數中,有如下片段:
if (!inVPre) {
processPre(element)
if (element.pre) {
inVPre = true
}
}
if (inVPre) {
processRawAttrs(element)
} else {
...
}
processPre
函數會獲取element
上的v-pre
屬性,如果有則設置element.pre = true
,同時設置inVPre = true
。
接下來的處理,會走進processRawAttrs
函數。else
塊內對各種指令、屬性等的處理,都不會執行。
function processRawAttrs (el) {
const l = el.attrsList.length
if (l) {
const attrs = el.attrs = new Array(l)
for (let i = 0; i < l; i++) {
attrs[i] = {
name: el.attrsList[i].name,
value: JSON.stringify(el.attrsList[i].value)
}
}
} else if (!el.pre) {
// non root node in pre blocks with no attributes
el.plain = true
}
}
這裏是對屬性的處理,如果el.attrsList
不為空數組,則直接循環el.attrsList
上的屬性添加到el.attrs
上。否則,如果當前元素沒有設置v-pre
指令(是設置v-pre
元素的子元素),則設置el.plain = true
。
因為我們不編譯的是整個子樹,而不是單個元素。Vue
中就是通過inVPre
來標示的,我們parse
的整個過程就是入棧出棧,當子元素都編譯完,會走到當前元素的end
處理,此時再設置inVPre = false
,來結束不編譯的內容。
v-text、v-html、v-cloak、v-pre.md