12.6-组件事件与 emit 的实现

147 阅读3分钟

emit 用来发射组件的自定义事件,如下面的代码所示:

const MyComponent = {
  name: "MyComponent",
  setup(props, { emit }) {
    // 发射 change 事件,并传递给事件处理函数两个参数
    emit("change", 1, 2)
    return () => {
      return // ...
    }
  },
}

当使用该组件时,我们可以监听由 emit 函数发射的自定义事 件:

 <MyComponent @change="handler" />

上面这段模板对应的虚拟 DOM 为:

const CompVNode = {
  type: MyComponent,
  props: {
    onChange: handler,
  },
}

可以看到,自定义事件 change 被编译成名为 onChange 的属 性,并存储在 props 数据对象中。这实际上是一种约定。作为框架设 计者,也可以按照自己期望的方式来设计事件的编译结果。

在具体的实现上,发射自定义事件的本质就是根据事件名称去 props 数据对象中寻找对应的事件处理函数并执行,如下面的代码所 示:

function mountComponent(vnode, container, anchor) {
  // 省略部分代码

  const instance = {
    state,
    props: shallowReactive(props),
    isMounted: false,
    subTree: null,
  }

  // 定义 emit 函数,它接收两个参数
  // event: 事件名称
  // payload: 传递给事件处理函数的参数
  function emit(event, ...payload) {
    // 根据约定对事件名称进行处理,例如 change --> onChange
    const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
    // 根据处理后的事件名称去 props 中寻找对应的事件处理函数
    const handler = instance.props[eventName]
    if (handler) {
      // 调用事件处理函数并传递参数
      handler(...payload)
    } else {
      console.error("事件不存在")
    }
  }

  // 将 emit 函数添加到 setupContext 中,用户可以通过 setupContext 取得 emit 函数
  const setupContext = { attrs, emit }

  // 省略部分代码
}

整体实现并不复杂,只需要实现一个 emit 函数并将其添加到 setupContext 对象中,这样用户就可以通过 setupContext 取得 emit 函数了。另外,当 emit 函数被调用时,我们会根据约定对事件 名称进行转换,以便能够在 props 数据对象中找到对应的事件处理函 数。最后,调用事件处理函数并透传参数即可。这里有一点需要额外 注意,我们在讲解 props 时提到,任何没有显式地声明为 props 的 属性都会存储到 attrs 中。换句话说,任何事件类型的 props,即 onXxx 类的属性,都不会出现在 props 中。这导致我们无法根据事件 名称在 instance.props 中找到对应的事件处理函数。为了解决这 个问题,我们需要在解析 props 数据的时候对事件类型的 props 做 特殊处理,如下面的代码所示:

function resolveProps(options, propsData) {
  const props = {}
  const attrs = {}
  for (const key in propsData) {
    // 以字符串 on 开头的 props,无论是否显式地声明,都将其添加到 props数据中,而不是添加到attrs 中
    if (key in options || key.startsWith("on")) {
      props[key] = propsData[key]
    } else {
      attrs[key] = propsData[key]
    }
  }

  return [props, attrs]
}

处理方式很简单,通过检测 propsDatakey 值来判断它是否 以字符串 'on' 开头,如果是,则认为该属性是组件的自定义事件。 这时,即使组件没有显式地将其声明为 props,我们也将它添加到最 终解析的 props 数据对象中,而不是添加到 attrs 对象中。