Vue3之事件綁定的實(shí)現(xiàn)邏輯詳解
Vue的事件綁定主要是通過(guò)v-on
指令來(lái)實(shí)現(xiàn)的,這個(gè)指令既可以實(shí)現(xiàn)原生事件綁定,例如onclick
等。
也可以實(shí)現(xiàn)組件的自定義事件,從而實(shí)現(xiàn)組件的數(shù)據(jù)通信。
本文我們就來(lái)分析下Vue的事件處理的邏輯。
v-on作用于普通元素
用在普通元素上時(shí),只能監(jiān)聽(tīng)原生 DOM 事件,最多的就是onclick
事件了。
我們就以onclick
事件來(lái)分析原理。
案例
let click = () => { console.log("點(diǎn)擊我,很快樂(lè)") }; <!-- template --> <div v-on:click="click">點(diǎn)擊我吧</div>
分析實(shí)現(xiàn)邏輯
我們先來(lái)看下渲染函數(shù)
const _hoisted_1 = ["onClick"] function render(_ctx, _cache) { with (_ctx) { const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", { onClick: click }, "點(diǎn)擊我吧", 8 /* PROPS */, _hoisted_1)) } }
我們看到 渲染函數(shù)在創(chuàng)建VNode的時(shí)候傳了一個(gè)onClick
的pros
;
我們先來(lái)看下patchProp
函數(shù)中對(duì)onClick
這個(gè)pros
的處理邏輯
export const patchProp: DOMRendererOptions['patchProp'] = ( el, key, prevValue, nextValue, isSVG = false, prevChildren, parentComponent, parentSuspense, unmountChildren ) => { if (isOn(key)) { patchEvent(el, key, prevValue, nextValue, parentComponent) } } function patchEvent(el, rawName, prevValue, nextValue, instance = null) { // vei = vue event invokers const invokers = el._vei || (el._vei = {}); if (添加) { const invoker = (invokers[rawName] = createInvoker(nextValue, instance)); el.addEventListener(event, handler, options) } else { el.removeEventListener(event, handler, options) } }
我們可以看出來(lái)底層就是調(diào)用的addEventListener
函數(shù)進(jìn)行事件監(jiān)聽(tīng)綁定,調(diào)用removeEventListener
進(jìn)行事件監(jiān)聽(tīng)解綁。
其實(shí)這個(gè)實(shí)現(xiàn)邏輯很容易想到,沒(méi)什么難度。
重點(diǎn)分析—事件修飾符
once
,capture
,passive
這兩個(gè)可以直接作為addEventListener
和removeEventListener
的第三個(gè)參數(shù)options
中的值,因?yàn)檫@是W3C支持的事件可選參數(shù)。
stop
, prevent
,capture
, self
等。
這類修飾符被封裝在另外一個(gè)withModifiers
函數(shù)中。
export const withModifiers = (fn: Function, modifiers: string[]) => { return (event: Event, ...args: unknown[]) => { for (let i = 0; i < modifiers.length; i++) { const guard = modifierGuards[modifiers[i]] if (guard && guard(event, modifiers)) return } return fn(event, ...args) } }
這里設(shè)計(jì)的非常精妙,每個(gè)修飾符都對(duì)應(yīng)一個(gè)執(zhí)行函數(shù),如果調(diào)用執(zhí)行函數(shù)guard(event, modifiers)
返回true
, 則函數(shù)withModifiers
就直接返回了,不會(huì)再執(zhí)行事件的函數(shù)fn(event, ...args)
了。
這里列一些這些修飾符對(duì)應(yīng)的函數(shù):
const modifierGuards: Record< string, (e: Event, modifiers: string[]) => void | boolean > = { stop: e => e.stopPropagation(), prevent: e => e.preventDefault(), self: e => e.target !== e.currentTarget, ctrl: e => !(e as KeyedEvent).ctrlKey, shift: e => !(e as KeyedEvent).shiftKey, alt: e => !(e as KeyedEvent).altKey, meta: e => !(e as KeyedEvent).metaKey, left: e => 'button' in e && (e as MouseEvent).button !== 0, middle: e => 'button' in e && (e as MouseEvent).button !== 1, right: e => 'button' in e && (e as MouseEvent).button !== 2, exact: (e, modifiers) => systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)) }
v-on作用于組件綁定自定義事件
實(shí)現(xiàn)案例
父組件中 有個(gè)子組件son
, 使用v-on
綁定了子組件的自定義事件,還有一個(gè)p
顯示當(dāng)前的時(shí)間戳。
<Son v-on:children-clicked="childClickedHandler" /> <p>{{ date }}</p> setup() { let childClickedHandler = (data: Date) => { date.value = data.getTime(); } let date = ref(new Date().getTime()); return { date, childClickedHandler }; },
子組件中有一個(gè)div
, 每次點(diǎn)擊會(huì)觸發(fā)自定義事件childrenClicked
, 并且傳遞了一個(gè)參數(shù)值為當(dāng)前時(shí)間。
<div v-on:click="clickevent">點(diǎn)擊我吧</div> emits: ["childrenClicked"], setup(props, {emit}) { let clickevent = () => { emit('childrenClicked', new Date()); } return {clickevent}; },
這樣點(diǎn)擊子組件后就會(huì)觸發(fā)父組件的childClickedHandler
方法,從而更新當(dāng)前時(shí)間戳的顯示。
接下來(lái)我們就來(lái)看看這底層的邏輯是如何實(shí)現(xiàn)的?
實(shí)現(xiàn)邏輯
先看下兩個(gè)組件的渲染函數(shù)的重點(diǎn)部分
父組件:
_createVNode(_component_Son, { onChildrenClicked: childClickedHandler }, null, 8 /* PROPS */, ["onChildrenClicked"])
父組件給子組件綁定自定義事件是傳遞了一個(gè)事件pro
,這個(gè)pro
的名稱用駝峰命名, 例如本例中的onChildrenClicked
。
子組件:
const _hoisted_1 = ["onClick"] _createElementBlock("div", { onClick: $event => ($emit('childrenClicked', new Date())) }, "點(diǎn)擊我吧", 8 /* PROPS */, _hoisted_1)
子組件div
點(diǎn)擊的綁定前面說(shuō)過(guò),點(diǎn)擊的時(shí)候執(zhí)行$emit('childrenClicked', new Date()
, 這個(gè)沒(méi)有什么特別的。
現(xiàn)在的問(wèn)題就是為什么子組件$emit('childrenClicked', new Date()
如何找到父組件的onChildrenClicked
方法并執(zhí)行?
$emit
來(lái)自于createSetupContext
函數(shù)調(diào)用時(shí)候傳入的參數(shù)setupContext
export function createSetupContext( instance: ComponentInternalInstance ): SetupContext { return { get attrs() { return attrs || (attrs = createAttrsProxy(instance)) }, slots: instance.slots, emit: instance.emit, expose } } }
$emit
就是組件實(shí)例的emit
方法。
實(shí)例的emit
方法用于尋找對(duì)應(yīng)的自定義事件的函數(shù)
export function emit( instance: ComponentInternalInstance, event: string, ...rawArgs: any[] ) { const props = instance.vnode.props || EMPTY_OBJ // 傳入的傳參 let args = rawArgs // TODO: 處理v-mode的方法 const isModelListener = event.startsWith('update:') // 處理函數(shù)名,on+首字母大寫的函數(shù)名 或者 on+駝峰命名的函數(shù)名 let handlerName let handler = props[(handlerName = toHandlerKey(event))] || props[(handlerName = toHandlerKey(camelize(event)))] if (!handler && isModelListener) { handler = props[(handlerName = toHandlerKey(hyphenate(event)))] } if (handler) { // 調(diào)用函數(shù),參數(shù)是外部傳入的參數(shù) callWithAsyncErrorHandling( handler, instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args ) } }
如果函數(shù)名以update:
開頭,說(shuō)明是一個(gè)v-model
的修改數(shù)據(jù)函數(shù),這部分邏輯會(huì)在v-model
專門的文章中介紹;
然后在實(shí)例對(duì)象的props
中找 on+首字母大寫的函數(shù)名 的函數(shù),如果沒(méi)找到,則找 on+首字母大寫且駝峰命名的函數(shù)名 的函數(shù);
如果找到了對(duì)應(yīng)的函數(shù),則調(diào)用函數(shù),調(diào)用函數(shù)的參數(shù)為傳入的參數(shù)。
總結(jié)
v-on
作用于普通元素底層是利用 addEventListener
和 removeEventListener
,修飾符要么利用W3C標(biāo)準(zhǔn),要么利用函數(shù)調(diào)用來(lái)實(shí)現(xiàn);
v-on
作用于組件是 子組件利用 emit
在 pro
中搜尋到對(duì)應(yīng)的函數(shù)(由父組件傳入),然后執(zhí)行對(duì)應(yīng)的函數(shù)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式
這篇文章主要介紹了vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02使用Vue3實(shí)現(xiàn)一個(gè)簡(jiǎn)單的思維導(dǎo)圖組件
思維導(dǎo)圖是一種用于表示信息、想法和概念的圖形化工具,本文將基于 Vue3和VueDraggable實(shí)現(xiàn)一個(gè)簡(jiǎn)單的思維導(dǎo)圖組件,支持節(jié)點(diǎn)拖拽,編輯及節(jié)點(diǎn)之間的關(guān)系連接,希望對(duì)大家有所幫助2025-04-04elementui彈窗頁(yè)按鈕重復(fù)提交問(wèn)題解決方法
本文主要介紹了elementui彈窗頁(yè)按鈕重復(fù)提交問(wèn)題解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問(wèn)題的方法
本文主要介紹了Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問(wèn)題的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06