Vue3中進行點擊事件埋點
函數(shù)埋點是一個常見的需求,之前進行埋點是通過 babel 進行解析插入或者手工進行添加
前幾天在某 babel 群里有人提出疑問:如何在 Vue 中對每個點擊事件插入一個函數(shù)?由于 .vue 文件是將 <template>、<script> 和 <style> 分開進行單獨解析,所以不能通過 babel 將監(jiān)聽函數(shù)準確解析出來(或許自己沒好好看文檔,不知道??♂?。要補補 webpack 了)。于是想了如下方法:
通過修改源碼的方式添加額外函數(shù)
如果公司有自己魔改的 Vue 框架,還是挺方便的。源碼版本為:3.2.20,位于 runtime-dom/src/modules/events.ts:
export function patchEvent(
el: Element & { _vei?: Record<string, Invoker | undefined> },
rawName: string,
prevValue: EventValue | null,
nextValue: EventValue | null,
instance: ComponentInternalInstance | null = null
) {
const invokers = el._vei || (el._vei = {})
const existingInvoker = invokers[rawName]
//TODO: 添加額外函數(shù)
if (nextValue && existingInvoker) {
// 更新監(jiān)聽函數(shù)
existingInvoker.value = nextValue
} else {
const [name, options] = parseName(rawName)
if (nextValue) {
// 添加監(jiān)聽函數(shù)
const invoker = (invokers[rawName] = createInvoker(nextValue, instance))
addEventListener(el, name, invoker, options)
} else if (existingInvoker) {
// 移除監(jiān)聽函數(shù)
removeEventListener(el, name, existingInvoker, options)
invokers[rawName] = undefined
}
}
}
在 patchEvent 方法中,通過 createInvoker 為監(jiān)聽函數(shù)封裝了一層用于執(zhí)行額外任務,隨后添加至元素的事件回調隊列中:
function createInvoker(
initialValue: EventValue,
instance: ComponentInternalInstance | null
) {
const invoker: Invoker = (e: Event) => {
const timeStamp = e.timeStamp || _getNow()
if (skipTimestampCheck || timeStamp >= invoker.attached - 1) {
callWithAsyncErrorHandling(
patchStopImmediatePropagation(e, invoker.value),
instance,
ErrorCodes.NATIVE_EVENT_HANDLER,
[e]
)
}
}
invoker.value = initialValue
invoker.attached = getNow()
return invoker
}
patchStopImmediatePropagation 的作用是:在 invoker.value 是數(shù)組的情況下生成新的監(jiān)聽函數(shù)數(shù)組,檢查 e._stopped 決定是否執(zhí)行監(jiān)聽函數(shù)
callWithAsyncErrorHandling 的作用是:執(zhí)行函數(shù)隊列,函數(shù)的參數(shù)為傳入的 第四個參數(shù)的解構
實現(xiàn)功能
function createInvokerValueWithSecretFunction (rawName: string, v: EventValue | null) {
if (!v) return v
const targetName = 'click'
const [name] = parseName(rawName)
const newValue: EventValue = isArray(v) ? v : [v]
if (name === targetName) {
newValue.unshift(insertFunction)
}
return newValue
function insertFunction (e: Event) {
console.log('Hello Click')
}
}
/**
* 完成之前的TODO
* - //TODO: 添加額外函數(shù)
* + nextValue = createInvokerValueWithSecretFunction(rawName, nextValue)
*/
通過 Vue 插件的方式添加額外函數(shù)
目的是在 生在渲染函數(shù) 時增加額外的轉換,將類似 @click="fn" 修改為 @click="[insertFn, fn]",更多細節(jié)待讀者完善。由于是在生成渲染函數(shù)時轉換,所以 自定義的渲染函數(shù)組件不支持 添加額外函數(shù)
增加額外編譯轉換函數(shù)可以直接修改實例配置,考慮和 babel 插件功能相似還是選擇單獨寫成 Vue 插件
相關源碼介紹,源碼版本為:3.2.20,位于 runtime-core/src/component.ts:
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
/* ... */
if (!instance.render) {
if (!isSSR && compile && !Component.render) {
/* ... */
if (template) {
/* ... */
const { isCustomElement, compilerOptions } = instance.appContext.config
const { delimiters, compilerOptions: componentCompilerOptions } =
Component
const finalCompilerOptions: CompilerOptions = extend(
extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
)
/* ... */
Component.render = compile(template, finalCompilerOptions)
}
}
/* ... */
}
/* ... */
}
在生成渲染函數(shù)前,會從 instance.appContext.config 即 Vue 實例的全局上下文的配置中獲取編譯配置對象 compilerOptions,也會從當前組件的配置對象獲取編譯配置對象 compilerOptions
位于 runtime-core/src/compile.ts:
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
/* ... */
const ast = isString(template) ? baseParse(template, options) : template
/* ... */
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
在生成完抽象語法樹后,通過 transform 進行源碼級別的轉換,并可以從中看到會合并使用者傳入的選項:options.nodeTransforms 和 options.directiveTransforms,兩者的區(qū)別在于前者是針對所有抽象語法樹的每個節(jié)點,后者是僅針對內置指令
實現(xiàn)功能
// 可自行修改,這里簡單描述
type Options = {
event: string
fn?: (e: Event) => void
}
function nodeTransformPluginForInsertFunction (app:App, options: Options = { event: 'click' }) {
const { event, fn } = options
if (typeof fn !== 'undefined' && typeof fn !== 'function') {
console.warn(/* 警告 */)
return
}
// 全局添加統(tǒng)一函數(shù),用于渲染函數(shù)獲取
const globalInsertFnName = '$__insertFunction__'
app.config.globalProperties[globalInsertFnName] = fn || defaultInsertFunction
const transformEventOfElement: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT /* 1 */ && node.props && node.props.length > 0) {
for (let i = 0; i < node.props.length; i++) {
const prop = node.props[i]
if (
/* 指令 */
prop.type === NodeTypes.DIRECTIVE /* 7 */ && prop.name === 'on'
/* 符合條件的指令 */
&& prop.arg && prop.arg.type === NodeTypes.SIMPLE_EXPRESSION /* 4 */ && prop.arg.content === event
/* 確保 exp 存在 */
&& prop.exp && prop.exp.type === NodeTypes.SIMPLE_EXPRESSION /* 4 */ && prop.exp.content.trim()
) {
let trimmedContent = prop.exp.content.trim()
// 將類似 `@click="fn"` 修改為 `@click="[insertFn, fn]"`
// 還需要考慮其他情況,文章這里就簡單處理了
if (trimmedContent[0] === '[') {
trimmedContent = `[${globalInsertFnName}, ${trimmedContent.substr(1)}`
} else {
trimmedContent = `[${globalInsertFnName}, ${trimmedContent}]`
}
prop.exp.content = trimmedContent
}
}
}
}
// 增加編譯轉換函數(shù)
const nodeTransforms: NodeTransform[] =
(app.config.compilerOptions as any).nodeTransforms|| ((app.config.compilerOptions as any).nodeTransforms = [])
nodeTransforms.push(transformEventOfElement)
function defaultInsertFunction (e: Event) {}
}
還有很多細節(jié)可以完善,可以參考內置的轉換函數(shù)讓自身的函數(shù)更加健壯
到此這篇關于Vue3中進行點擊事件埋點的文章就介紹到這了,更多相關Vue3 點擊事件埋點內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue3使用threejs實現(xiàn)3D卡片水平旋轉效果的示例代碼
這篇文章主要介紹了在vue3中使用threejs實現(xiàn)3D卡片水平旋轉效果,文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-04-04
Vue中對比scoped css和css module的區(qū)別
這篇文章主要介紹了Vue中scoped css和css module的區(qū)別對比,scoped css可以直接在能跑起來的vue項目中使用而css module需要增加css-loader配置才能生效。具體內容詳情大家參考下本文2018-05-05
element-ui table span-method(行合并)的實現(xiàn)代碼
element-ui官網(wǎng)中關于行合并的例子是根據(jù)行號進行合并的,這顯然不符合我們日常開發(fā)需求,因為通常我們table中的數(shù)據(jù)都是動態(tài)生成的,非常具有實用價值,需要的朋友可以參考下2018-12-12
Vue使用Print.js打印div方式(選中區(qū)域的html)
這篇文章主要介紹了Vue使用Print.js打印div方式(選中區(qū)域的html),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
vue cli3 調用百度翻譯API翻譯頁面的實現(xiàn)示例
這篇文章主要介紹了vue cli3 調用百度翻譯API翻譯頁面的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09

