# vue源码 事件机制

# 模版编译

  • 在编译阶段对事件指令做收集
  • processAttrs /src/compiler/parser/index.js
export const dirRE = process.env.VBIND_PROP_SHORTHAND
  ? /^v-|^@|^:|^\.|^#/
  : /^v-|^@|^:|^#/

function processAttrs (el) {
  const list = el.attrsList
  ...
    for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) { // 匹配 
        ...      // modifiers
      modifiers = parseModifiers(name.replace(dirRE, ''))
        ...
      if (bindRE.test(name)) { // v-bind
        name = name.replace(bindRE, '')
            ...
           
      } else if (onRE.test(name)) { // v-on
        name = name.replace(onRE, '')
        isDynamic = dynamicArgRE.test(name)// 动态绑定
        if (isDynamic) {
          name = name.slice(1, -1)
        }
        addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
      } else { // normal directives
      ...
    } else {
      // literal attribute
     
    }
  }
}
  • addHandler 为ast树添加事件相关属性 /compiler/helpers.js
  • generate 最终转换成ast树后生成render渲染函数 /compiler/codegen/index.js
export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  // fix #11483, Root level <script> tags should not be rendered.
  const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")' //here
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}
  • genElement对普通模版分支会进入genData
...
  // component or element
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData(el, state)
      }
...
  • genData()
export function genData (el: ASTElement, state: CodegenState): string {
...
 // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false)},`
  }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true)},`
  }
  ...
  • genHandlers() 遍历解析好的ast树,并拿到event对象,根据属性凭借字符串
export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean
): string {
  const prefix = isNative ? 'nativeOn:' : 'on:'
  let staticHandlers = ``
  let dynamicHandlers = ``
  for (const name in events) {
    const handlerCode = genHandler(events[name]) //here
    if (events[name] && events[name].dynamic) {
      dynamicHandlers += `${name},${handlerCode},`
    } else {
      staticHandlers += `"${name}":${handlerCode},`
    }
  }
  ...
  • genHandler()
function genHandler (handler: ASTElementHandler | Array<ASTElementHandler>): string {
  if (!handler) {
    return 'function(){}'
  }

  if (Array.isArray(handler)) {
    return `[${handler.map(handler => genHandler(handler)).join(',')}]`
  }
  //value : dothis
  const isMethodPath = simplePathRE.test(handler.value) //@click = dothis
  const isFunctionExpression = fnExpRE.test(handler.value) // @click = ()=>{}
  const isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')) // @click = dothis(event)
...
}

# 事件绑定

  • createElm /vdom/patch.js
...
if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
...
  • invokeCreateHooks
 function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](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)
    }
  }
  • updateDOMListeners
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (!oldVnode.data.on && !vnode.data.on) {
    return
  }
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  target = vnode.elm
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}

export default {
  create: updateDOMListeners,
  update: updateDOMListeners
}

# 关于组件通信

  • 本质都是在当前组件实例里进行
  • 能有通信的效果时因为事件监听的回调写在了父组件中
Last Updated: 2021/9/22 下午7:44:44