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