VUE Source - Event Processing

VUE is how to deal with incidents

In everyday development, we @click with dancing, components custom event enables communication between father and son components that we have thought about them achieve what principle is it? Next we will explore the mysteries of native events and custom events. With questions start line and source code.  

First, try some test code, test code, we include native events, and custom events

<body> 
  <div the above mentioned id = "App"> 
    <h1> Event Processing </ h1> 
    <-! normal event -> 
    <@ the p-the Click = 'onclick'> normal event </ the p-> 
    <-! Customize events -> 
    <CoMP @ MyClick = "onMyClick"> </ CoMP> 
  </ div> 
</ body> 
<Script> 
  Vue.component ( 'CoMP' , { 
    Template: ` <div = @ the Click" the onClick "> the this CoMP IS </ div> `,
     Methods: { 
      the onClick () { 
        the this . $ EMIT ( 'MyClick' ) 
      } 
    } 
  }) 
  const App = new new Vue ({ 
    EL:'#app',
    methods: {
      onclick() {
        console.log('Normal Event " ); 
      }, 
      onMyClick () { 
        the console.log ( ' custom event ' ); 
      } 
    }, 
  }) 
  the console.log (App $ options.render);.
 </ Script>

Before Vue mounted done a lot of work to compile, the compiled template template to render function, this process can not do too much to explain. We look at the main production render after the function is how to achieve the binding event.

Let's observe the print out of app. $ Options.render  results

(function anonymous() {
    with(this) {
      return _c('div', {
        attrs: {
          "id": "app"
        }
      }, [_c('h1', [_v("事件处理")]), _v(" "), _c('div', {
        on: {
          "click": onclick
        }
      }, [_v("普通事件")]), _v(" "), _c('comp', {
        on: {
          "myclick": onMyClick
        }
      })], 1)
    }
  })

According to the results print of view, ordinary events and custom events generated results almost the same, will handle the event placed on top.

Ordinary events

As far as I know, Vue time component initialization, the event will be listening in native platforms \ web \ runtime \ modules \ events.js inside, will perform updateDOMListeners method.  

Want to know to test, whether to perform this function, we can break point to test the function inside. 

 

 

You can see that we will succeed in entering, call flow that want to know, we can look at the stack inside information.

Because with Vnode later, it will traverse the child nodes recursively call createElm create true for each child node DOM , created in real DOM when will form the associated hook invokeCreateHooks . Including the registration process events updateDOMListeners .       

 

 Into invokeCreateHooks function  

function invokeCreateHooks (vnode, insertedVnodeQueue) {
  for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
    cbs.create[i$1](emptyNode, vnode);
  }
  i = vnode.data.hook; // Reuse variable
  if (isDef(i)) {
    if (isDef(i.create)) { i.create(emptyNode, vnode); }
    if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
  }
}

We can look at what the hook function will be executed.

 

 We can see that in invokeCreateHooks  function which is to perform all over the hook function, among them updateDOMListeners .   

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 || {}
  target = vnode.elm
  // 兼容性处理
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}

Wherein normalizeEvents Is the v-model  compatibility processing in IE without the  input  support only change event, the input replace event into a change event.           

if (isDef(on[RANGE_TOKEN])) {
    // IE input[type=range] only supports `change` event
    const event = isIE ? 'change' : 'input'
    on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])
    delete on[RANGE_TOKEN]
  }

updateListeners  logic is not complicated, it will traverse on the event binding event registration event on the new node, remove old event node listening.

 

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    ...
    //  执行真正注册事件的执行函数
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

add  function, in real DOM binding events, its implementation also use native DOM  's addEventListener    

function add (
  name: string,
  handler: Function,
  capture: boolean,
  passive: boolean
) {
  ...
  
  target.addEventListener(
    name,
    handler,
    supportsPassive
      ? { capture, passive }
      : capture
  )
}

 

 

At a glance, to add the event to the click event native and listeners realized. These are the ordinary process of binding events.

Custom Event

We know that the father and son components can communicate using events, sub-assemblies by $ emit vm. Dispatch events to parent component, the parent component by v-on: (event) to receive information and handle the callback.

Examples can be seen from the beginning, the primary use of the common node DOM events, custom events may be used on the component, the additional component may also be used native event, with .native  modifier distinguished. Next we look at how a custom event is handled. 

In Vnode process of generating real node, this process encounters sub Vnode instantiates annihilator component instance. Process instance subclasses constructor, there will be options to configure the initialization process, will enter into Vue.prototype.init , we look directly for custom event processing. In the  src \ core \ instance \ init.js in

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    ...
    // merge options
    // 针对子组件的事件处理
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    // 初始化事件处理
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    ...
  }

Into the event handler initEvents , inside the processing logic it is relatively simple, just a few lines of code. 

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

We hit the breakpoint function inside look 

 

 For the first time into the time, we see the current root component is created, the root component _uid: 0, we let go once again into now see is our custom component is created. This time listeners will be stored in.   

 

 Next will come out updateComponentListeners , custom event processing. 

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

Simply look at the code, the current component instance is assigned to the target object target , then the event listener. 

Also, there will add a function to perform, that here add and native events and different, we can guess what, here's add is how to deal with.  

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    ...
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

Might guess, and by  $ on  for event listeners

function add (event, fn) {
  target.$on(event, fn)
}

We can see, custom events, although the statement is to monitor the event up in the parent component, but still listens on the child component is listening, who distributed the justice, who is listening.

That would doubt that he distributed he listens, and that is how the parent component by communicating it? It should be noted here, the callback function is declared in the parent component.

We think, sub-assemblies is how to get the custom event of the parent component of it, in fact, updateComponentListeners  in vm. $ Options._parentListeners , you can get a custom event of the parent component. So _parentListeners is how come?   

In fact, _init method, the execution initEvents before, the components will be processed. initInternalComponent (vm, options)  

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

In the assembly of the parent component inside vnodeComponentOptions inside listeners incident is defined custom components inside, MyClick , so you can get inside the sub-assembly, and then bound on the event.    

to sum up

Will be in the form of property in the template compilation stage, the stage will go a real render node binding-related events based on event properties. For custom event components, we can communicate between father and son components with events, and its essence is in the interior subassembly distribute their own events, listen for events. To achieve the effect of communication, because the callback function is declared in the parent components.

Guess you like

Origin www.cnblogs.com/DivHao/p/11806322.html