事件处理
- 普通元素的事件,是在
invokeInsertHook中updateDOMListeners,通过原生addEventListener添加的 - 组件的事件,是在初始组件实例
initEvents中updateComponentListeners,通过$on添加的
let Child = {
template: `
<button @click="clickHandler($event)">
click me
</button>
`,
methods: {
clickHandler(e) {
console.log('Button clicked!', e)
this.$emit('select')
this.$emit('my')
}
}
}
let vm = new Vue({
el: '#app',
template: `
<div id="test">
<child @select="selectHandler" @my="selectHandler" @click.native.prevent="clickHandler"></child>
</div>
`,
data(){
return {
msg: 'dxx'
}
},
methods: {
clickHandler() {
console.log('Child clicked!')
},
selectHandler() {
console.log('Child select!')
}
},
components: {
Child
}
})
1、普通元素上绑定原生事件
比如上面的 <button @click="clickHandler($event)">
2、组件上可以自定义事件和绑定原生事件
上例 child组件自定义事件 select,而绑定原生事件需要加上修饰符native
Vue 怎么处理:
在 vue/src/compiler/to-function.js 的 createCompileToFunctionFn 中可以看到编译 template的结果。
render:'with(this){return _c('div',{attrs:{"id":"test"}},[_c('child',{on:{"select":selectHandler,"my":selectHandler},nativeOn:{"click":function($event){$event.preventDefault();return clickHandler.apply(null, arguments)}}})],1)}'
参数传递
// a='child' b={on:{},nativeOn:{}} c d undefined
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
createElement (
context: Component,
tag: any, // a
data: any, // b
children: any, // c
normalizationType: any, // d
alwaysNormalize: boolean // false
)
_createElement(context, tag, data, children, normalizationType)
createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
}
在 createComponent 中
const listeners = data.on
data.on = data.nativeOn
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
childVnode 创建完,再处理 divVnode
如下:
childVnode 是组件,patch 时,createComponent -> initHook,子组件初始化,处理自定义事件
function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// 这边是 childVnode._parentListeners 也是 $listeners
// 是在子组件初始化 initInternalComponent 中 opts._parentListeners = vnodeComponentOptions.listeners
// 就是原来 data.on 自定义事件,
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
// 更改 target
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
真正添加事件的函数是 updateListeners,这边组件自定义事件通过 $on 添加。
子组件 $mount 时 render 函数
render:'with(this){return _c('button',{on:{"click":function($event){return clickHandler($event)}}},[_v("\\n click me\\n ")])}'
_v 是 createTextVNode 创建文本 Vnode,然后创建 buttonVnode,生成如下:
button 事件的添加是在这里
patch:
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// updateDOMListeners 中添加 vue/src/platforms/web/runtime/modules/events.js
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// children 没有事件,处理完后到 button 中 data: {on:{click:fn}}
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
// vnode is empty when removing all listeners,
// and use old vnode dom element
target = vnode.elm || oldVnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
function add (
name: string,
handler: Function,
capture: boolean,
passive: boolean
) {
if (useMicrotaskFix) {
const attachedTimestamp = currentFlushTimestamp
const original = handler
target.addEventListener(
name,
handler,
supportsPassive
? { capture, passive }
: capture
)
}
function remove (
name: string,
handler: Function,
capture: boolean,
_target?: HTMLElement
) {
(_target || target).removeEventListener(
name,
handler._wrapper || handler,
capture
)
}
function createOnceHandler (event, handler, capture) {
const _target = target // save current target element in closure
return function onceHandler () {
const res = handler.apply(null, arguments)
if (res !== null) {
remove(event, onceHandler, capture, _target)
}
}
}
添加事件调的原生 addEventListener 。
到这边,组件自定义事件、button 原生事件已添加,还差组件原生事件添加。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
当子组件 DOM 生成后,回到 childVnode ,此时 componentInstance 有值,initComponent 中触发 invokeCreateHooks 和 button 时一样。将事件添加到 button 中,childVnode.elm 也是 button 元素。
v-model
1、用在表单元素上
let vm = new Vue({
el: '#app',
template: `
<div id="test">
<input v-model="msg" />
{{msg}}
</div>
`,
data() {
return {
msg: 'dxx'
}
},
});
v-model 编译后的样子
表单:
render:'with(this){return _c('div',{attrs:{"id":"test"}},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(msg),expression:"msg"}],domProps:{"value":(msg)},on:{"input":function($event){if($event.target.composing)return;msg=$event.target.value}}}),_v("\\n "+_s(msg)+"\\n ")])}'
2、组件上:
let Child = {
template: `
<div>
<input :value="value" @input="updateValue" >
</div>
`,
props:['value'],
methods: {
updateValue(e) {
this.$emit('input', e.target.value)
}
}
}
let vm = new Vue({
el: '#app',
template: `
<div id="test">
<child v-model="msg"></child>
{{msg}}
</div>
`,
data() {
return {
msg: 'dxx'
}
},
components: {
Child
}
})
render:'with(this){return _c('div',{attrs:{"id":"test"}},[_c('child',{model:{value:(msg),callback:function ($$v) {msg=$$v},expression:"msg"}}),_v("\\n "+_s(msg)+"\\n ")],1)}'
{
model:{
value:(msg),
callback:function ($$v) {msg=$$v},
expression:"msg"
}
}
// 创建组件 Vnode createComponent
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
处理后
data {
attrs:{value: 'dxx'},
model: {value:(msg)callback:function ($$v) {msg=$$v},expression:"msg",},
on: {input: data.model.callback}
}
propsData:{value: 'dxx'}
listeners = data.on
data.on = data.nativeOn
{
attrs:{}
hook:{init: ƒ, prepatch: ƒ, insert: ƒ, destroy: ƒ}
model:{value: 'dxx', callback: ƒ, expression: 'msg'}
on:undefined
}
render:'with(this){return _c('div',[_c('input',{domProps:{"value":value},on:{"input":updateValue}})])}'