Vue編譯器中的過濾器轉(zhuǎn)換機(jī)制(transformFilter)詳解
一、背景:為什么需要 transformFilter?
在 Vue 2 中,我們可以寫出這樣的模板:
{{ message | capitalize }}
過濾器(Filter)語法允許開發(fā)者在模板中使用管道符(|)對數(shù)據(jù)進(jìn)行格式化。
但在 Vue 3 中,這個(gè)語法被移除了,因?yàn)樗屇0暹壿嬇c展示層混雜,不利于維護(hù)。
為了讓舊項(xiàng)目平滑遷移到 Vue 3,官方在“兼容模式(compat mode) ”中保留了該特性,而 transformFilter 就是負(fù)責(zé) 在編譯時(shí) 把 | 表達(dá)式“翻譯”為函數(shù)調(diào)用的核心邏輯。
二、入口函數(shù):transformFilter()
export const transformFilter: NodeTransform = (node, context) => {
if (!isCompatEnabled(CompilerDeprecationTypes.COMPILER_FILTERS, context)) {
return
}
if (node.type === NodeTypes.INTERPOLATION) {
rewriteFilter(node.content, context)
} else if (node.type === NodeTypes.ELEMENT) {
node.props.forEach((prop) => {
if (
prop.type === NodeTypes.DIRECTIVE &&
prop.name !== 'for' &&
prop.exp
) {
rewriteFilter(prop.exp, context)
}
})
}
}
作用解析:
僅在兼容模式下生效(通過 isCompatEnabled() 判斷)。
對兩種類型的節(jié)點(diǎn)進(jìn)行處理:
- 插值表達(dá)式(如
{{ msg | upper }}) - 指令表達(dá)式(如
v-bind:title="msg | trim")
每個(gè)匹配到的表達(dá)式都會交給 rewriteFilter() 進(jìn)行進(jìn)一步分析。
三、遞歸分析:rewriteFilter()
function rewriteFilter(node: ExpressionNode, context: TransformContext) {
if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
parseFilter(node, context)
} else {
for (let child of node.children) {
if (typeof child !== 'object') continue
if (child.type === NodeTypes.SIMPLE_EXPRESSION) {
parseFilter(child, context)
} else if (child.type === NodeTypes.COMPOUND_EXPRESSION) {
rewriteFilter(node, context)
} else if (child.type === NodeTypes.INTERPOLATION) {
rewriteFilter(child.content, context)
}
}
}
}
思路講解:
- 簡單表達(dá)式(如
msg | upper)直接進(jìn)入下一步解析。 - 復(fù)合表達(dá)式(如
msg + "!" | wrap)則遞歸展開所有子節(jié)點(diǎn),逐層掃描是否存在過濾器語法。 - 這種遞歸保證了在任意嵌套層級的表達(dá)式中都能正確識別過濾器。
四、核心邏輯:parseFilter()
function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
const exp = node.content
let inSingle = false, inDouble = false, inTemplateString = false
let inRegex = false
let curly = 0, square = 0, paren = 0
let lastFilterIndex = 0
let expression, filters: string[] = []
for (let i = 0; i < exp.length; i++) {
const c = exp.charCodeAt(i)
...
if (c === 0x7c && !curly && !square && !paren) {
if (expression === undefined) {
lastFilterIndex = i + 1
expression = exp.slice(0, i).trim()
} else {
pushFilter()
}
}
}
if (expression === undefined) {
expression = exp.trim()
} else if (lastFilterIndex !== 0) {
pushFilter()
}
if (filters.length) {
for (let f of filters) {
expression = wrapFilter(expression, f, context)
}
node.content = expression
}
}
關(guān)鍵邏輯說明:
狀態(tài)機(jī)掃描:
遍歷表達(dá)式的每個(gè)字符,記錄當(dāng)前是否處于:
- 單引號
'...' - 雙引號
"..." - 模板字符串
`...` - 正則表達(dá)式
/.../ - 括號或數(shù)組中
避免誤把字符串內(nèi)的|當(dāng)作過濾器分隔符。
識別管道符:
當(dāng)遇到頂層 |(非 ||),并且不在任何括號中時(shí),認(rèn)為是一個(gè)過濾器的起點(diǎn)。
拆分表達(dá)式與過濾器:
將 message | capitalize | trim(10) 拆為:
expression = "message" filters = ["capitalize", "trim(10)"]
重組表達(dá)式:
通過 wrapFilter() 將過濾器按從左到右順序包裝成嵌套調(diào)用:
toValidAssetId('capitalize', 'filter')(
toValidAssetId('trim', 'filter')(message)
)
五、封裝函數(shù):wrapFilter()
function wrapFilter(exp: string, filter: string, context: TransformContext) {
context.helper(RESOLVE_FILTER)
const i = filter.indexOf('(')
if (i < 0) {
context.filters!.add(filter)
return `${toValidAssetId(filter, 'filter')}(${exp})`
} else {
const name = filter.slice(0, i)
const args = filter.slice(i + 1)
context.filters!.add(name)
return `${toValidAssetId(name, 'filter')}(${exp}${args !== ')' ? ',' + args : args}`
}
}
功能說明:
- 檢查過濾器是否帶參數(shù)(例如
truncate(10))。 - 調(diào)用
toValidAssetId()將過濾器名轉(zhuǎn)為合法標(biāo)識符。 - 將原表達(dá)式
exp作為第一個(gè)參數(shù)注入。 - 返回新的函數(shù)調(diào)用字符串。
六、對比:Vue 2 與 Vue 3 的不同
| 特性 | Vue 2 | Vue 3(兼容模式) | Vue 3(標(biāo)準(zhǔn)模式) |
|---|---|---|---|
| 過濾器語法 | 支持 ` | ` | 支持(內(nèi)部轉(zhuǎn)換) |
| 編譯時(shí)行為 | 直接編譯為 _f("filterName")(exp) | 重寫為函數(shù)調(diào)用 | 不處理 |
| 性能影響 | 中等 | 低(僅兼容模式觸發(fā)) | 無 |
七、實(shí)踐示例
// 輸入模板
{{ message | capitalize | append('!') }}
// 編譯后表達(dá)式
_append(
_capitalize(message),
'!'
)
解釋:
編譯器會把 | 鏈?zhǔn)秸{(diào)用轉(zhuǎn)換為多層函數(shù)嵌套調(diào)用,保持與 Vue 2 的行為一致。
八、拓展與啟發(fā)
這個(gè)模塊雖然只是為兼容而存在,但它體現(xiàn)了 Vue 編譯器的幾個(gè)關(guān)鍵思想:
- 語法兼容通過 AST 轉(zhuǎn)換實(shí)現(xiàn),而非運(yùn)行時(shí)兼容。
- 通過狀態(tài)機(jī)掃描避免誤判字符。
- 函數(shù)式封裝(wrapFilter)保持代碼純凈與擴(kuò)展性。
九、潛在問題與注意事項(xiàng)
- 性能開銷:每個(gè)表達(dá)式都要字符級掃描,模板過大會影響編譯性能。
- 語法歧義:若表達(dá)式中本身含
|(非過濾器用途),可能被誤判。 - 未來?xiàng)売?/strong>:Vue 3 官方已聲明
filter將在未來版本完全移除,開發(fā)者應(yīng)盡早改用 計(jì)算屬性或方法替代。
結(jié)語:
transformFilter 是 Vue 3 兼容模式中的一個(gè)“橋梁模塊”,它讓舊項(xiàng)目無需改動(dòng)即可運(yùn)行在新架構(gòu)上,同時(shí)展示了 Vue 編譯器在語法轉(zhuǎn)換上的高靈活性。
以上就是Vue編譯器中的過濾器轉(zhuǎn)換機(jī)制(transformFilter)詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue過濾器轉(zhuǎn)換機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue3.0結(jié)合bootstrap創(chuàng)建多頁面應(yīng)用
這篇文章主要介紹了Vue3.0結(jié)合bootstrap創(chuàng)建多頁面應(yīng)用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
vue添加錨點(diǎn),實(shí)現(xiàn)滾動(dòng)頁面時(shí)錨點(diǎn)添加相應(yīng)的class操作
這篇文章主要介紹了vue添加錨點(diǎn),實(shí)現(xiàn)滾動(dòng)頁面時(shí)錨點(diǎn)添加相應(yīng)的class操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Element-ui?DatePicker日期選擇器基礎(chǔ)用法示例
這篇文章主要為大家介紹了Element-ui?DatePicker日期選擇器基礎(chǔ)用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
vue計(jì)算屬性computed方法內(nèi)傳參方式
這篇文章主要介紹了vue計(jì)算屬性computed方法內(nèi)傳參方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue使用keep-alive實(shí)現(xiàn)數(shù)據(jù)緩存不刷新
這篇文章主要介紹了vue使用keep-alive實(shí)現(xiàn)數(shù)據(jù)緩存不刷新,這里整理了詳細(xì)的代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
vue項(xiàng)目的創(chuàng)建的步驟(圖文教程)
本文主要介紹了vue項(xiàng)目的創(chuàng)建的步驟(圖文教程),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Vue實(shí)現(xiàn)PopupWindow組件詳解
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)PopupWindow組件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04

