[Vue 源码] Vue 3.2 - emit 原理

185 阅读1分钟

代码运行结果

emit 原理.gif

代码示例

    <script>
      let { createApp, reactive, toRefs, h } = Vue

      const useCount = () => {
        const state = reactive({ count: 0 })
        const setCount = () => {
          state.count++
        }

        return { state, setCount }
      }

      const MyCpn = {
        props: {
          count: {},
          onChildUpdate: {}
        },
        setup(props, { emit }) {
          return () =>
            h(
              'button',
              {
                onClick: () => {
                  emit('childUpdate')
                }
              },
              [props.count]
            )
        }
      }

      const App = {
        setup() {
          let { state, setCount } = useCount()
          return {
            ...toRefs(state),
            setCount
          }
        },

        render() {
          return h(MyCpn, {
            count: this.count,
            onChildUpdate: () => {
              this.setCount()
            }
          })
        }
      }

      createApp(App).mount('#app')
    </script>

emit 原理

第一:挂载逻辑可以看以前的文章:Vue 组件挂载原理, 这里我们聊关键的部分。 image.png

  • 挂载子组件的时候,将 count, onChildUpdate 自定义事件函数 作为了子组件的 props, 根据上图也可以得知。
  • 当我们在子组件中 emit 的时候,实际上调用调用的是 instance.emit 函数。
    • 在 emit 函数中,对于本例子来说,emit("childUpdate"), emit 函数会将 childUpdate 进行 handleKey, camelize 函数处理得到 onChildUpdate 事件名字,然后去 Props 当中去寻找。并且调用。
    • 另一种,情况是 v-model 的情况,也就是以update 开头的事件名字,Vue 同样也会对这种情况从 Props 中去找 update:modleValue 事件名, 同时也会找受控值 modelValue。
export function emit(
  instance: ComponentInternalInstance,
  event: string,
  ...rawArgs: any[]
) {
  const props = instance.vnode.props || EMPTY_OBJ

  let args = rawArgs
  const isModelListener = event.startsWith('update:')

  // for v-model update:xxx events, apply modifiers on args
  const modelArg = isModelListener && event.slice(7)
  if (modelArg && modelArg in props) {
    const modifiersKey = `${
      modelArg === 'modelValue' ? 'model' : modelArg
    }Modifiers`
    const { number, trim } = props[modifiersKey] || EMPTY_OBJ
    if (trim) {
      args = rawArgs.map(a => (isString(a) ? a.trim() : a))
    }
    if (number) {
      args = rawArgs.map(looseToNumber)
    }
  }

  let handlerName
  let handler =
    props[(handlerName = toHandlerKey(event))] ||
    // also try camelCase event handler (#2249)
    props[(handlerName = toHandlerKey(camelize(event)))]
  // for v-model update:xxx events, also trigger kebab-case equivalent
  // for props passed via kebab-case
  if (!handler && isModelListener) {
    handler = props[(handlerName = toHandlerKey(hyphenate(event)))]
  }

  if (handler) {
    callWithAsyncErrorHandling(
      handler,
      instance,
      ErrorCodes.COMPONENT_EVENT_HANDLER,
      args
    )
  }