Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 3 事件 #47

Open
imfenghuang opened this issue Jun 24, 2024 · 0 comments
Open

Vue 3 事件 #47

imfenghuang opened this issue Jun 24, 2024 · 0 comments

Comments

@imfenghuang
Copy link
Owner

imfenghuang commented Jun 24, 2024

// packages/runtime-core/src/renderer.ts
// createApp -> createAppAPI -> mount -> render -> patch -> processElement -> mountElement -> hostPatchProp(即 patchProp)
// packages/runtime-core/src/renderer.ts
const patchProps = (
  el: RendererElement,
  vnode: VNode,
  oldProps: Data,
  newProps: Data,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  namespace: ElementNamespace,
) => {
  // 如果 oldProps !== newProps
  if (oldProps !== newProps) {
    if (oldProps !== EMPTY_OBJ) {
      for (const key in oldProps) {
        // 非 vue 保留 prop 且 不在 newProps 的 prop
        if (!isReservedProp(key) && !(key in newProps)) {
          // 实际执行 renderer options 上的 patchProp 方法
          // packages/runtime-dom/src/index.ts
          // import { patchProp } from './patchProp' 
          // const { patchProp: hostPatchProp } = options
          hostPatchProp(
            el,
            key,
            oldProps[key],
            null,
            namespace,
            vnode.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren,
          )
        }
      }
    }
    for (const key in newProps) {
      // 判断是否非 vue 保留 prop(空字符串也在保留字符串内)
      if (isReservedProp(key)) continue
      const next = newProps[key]
      const prev = oldProps[key]
      // prop 对应新值不等于旧值,且 prop 不是 value
      if (next !== prev && key !== 'value') {
        hostPatchProp(
          el,
          key,
          prev,
          next,
          namespace,
          vnode.children as VNode[],
          parentComponent,
          parentSuspense,
          unmountChildren,
        )
      }
    }
    // 单独处理 prop 是 value 的情况
    if ('value' in newProps) {
      hostPatchProp(el, 'value', oldProps.value, newProps.value, namespace)
    }
  }
}

// 具体的 patchProp 方法
const patchProp: DOMRendererOptions['patchProp'] = (
  el,
  key,
  prevValue,
  nextValue,
  namespace,
  prevChildren,
  parentComponent,
  parentSuspense,
  unmountChildren
) => {
  const isSVG = namespace === "svg";
  if (key === "class") {
    // 处理 class
    patchClass(el, nextValue, isSVG);
  } else if (key === "style") {
    // 处理 style
    patchStyle(el, prevValue, nextValue);
  } else if (isOn(key)) {
    // 处理事件,忽略 v-model 的情况
    // 事件的 patch 看这里
    if (!isModelListener(key)) {
      patchEvent(el, key, prevValue, nextValue, parentComponent);
    }
  } else if (
    key[0] === "."
      ? ((key = key.slice(1)), true)
      : key[0] === "^"
        ? ((key = key.slice(1)), false)
        : shouldSetAsProp(el, key, nextValue, isSVG)
  ) {
    patchDOMProp(
      el,
      key,
      nextValue,
      prevChildren,
      parentComponent,
      parentSuspense,
      unmountChildren
    );
    // #6007 also set form state as attributes so they work with
    // <input type="reset"> or libs / extensions that expect attributes
    if (key === "value" || key === "checked" || key === "selected") {
      patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value");
    }
  } else {
    // special case for <input v-model type="checkbox"> with
    // :true-value & :false-value
    // store value as dom properties since non-string values will be
    // stringified.
    // 对于 <input v-model type="checkbox"> 且带有 :true-value :false-value 的特殊处理
    if (key === "true-value") {
      el._trueValue = nextValue;
    } else if (key === "false-value") {
      el._falseValue = nextValue;
    }

    patchAttr(el, key, nextValue, isSVG, parentComponent);
  }
};

// 具体的 patchEvent
const veiKey = Symbol('_vei')
function patchEvent(
  el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
  rawName: string,
  prevValue: EventValue | null,
  nextValue: EventValue | unknown,
  instance: ComponentInternalInstance | null = null,
) {
  // vei = vue event invokers
  const invokers = el[veiKey] || (el[veiKey] = {})
  const existingInvoker = invokers[rawName]
  if (nextValue && existingInvoker) {
    // 如果存在 新的事件处理函数 且 已经存在 invoker 方法,则把新的处理函数复制到 invoker 方法的 value 
    existingInvoker.value = (nextValue as EventValue)
  } else {
    // 解析事件名称 和 事件 options
    // onClick:Capture = () => console.log(1)
    // name => click
    // options = {
    //   capture: true
    // }
    const [name, options] = parseName(rawName)
    if (nextValue) {
      // 新增事件监听
      /*****
      invoker 是一个伪造的事件处理函数,用来处理提升给事件打补丁时的性能,因为如果不这样处理,那么在打补丁的时候,需要先移除监听,再重新监听。而 invoker 处理之后,通过把事件的处理方法直接替换的方法,减少了一次 移除监听
      
      // 伪代码
      const createInvoker = (nextValue) => {
      const invoker = (e) => {
        // e.timestmap
        if (!e._vts) {
        // 事件发生时间
          e._vts = Date.now() 
        } else if (e._vts <= invoker.attached) {
          // 如果事件发生事件 遭遇 事件处理函数绑定的时间,则不执行事件处理函数
          return
        }
  	
        // 执行事件绑定的函数
        if (Array.isArray(invoker.value)) {
          invoker.value.map(fn => fn(e))
        } else {
          invoker.value(e)
        }
      }
  	
      // nextValue 真正的事件处理函数
      invoker.value = nextValue;
      // 事件处理函数的
      invoker.attached = Date.now();
      return invoker
    }

    *****/
      const invoker = (invokers[rawName] = createInvoker((nextValue as EventValue), instance,))
      addEventListener(el, name, invoker, options)
    } else if (existingInvoker) {
      // 移除事件监听
      removeEventListener(el, name, existingInvoker, options)
      invokers[rawName] = undefined
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant