// 问题:在vue子组件内部使用的方法中调用 this.$emit('some-event', ...args) 是如何触发父组件的方法呢?
// 先看下 this是什么:组件实例对象的proxy属性
// proxy属性时什么:组件实例对象ctx属性的代理
// 看下代理返回啥
/**
* const publicPropertiesMap = extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (shallowReadonly(i.props) ),
$attrs: i => (shallowReadonly(i.attrs) ),
$slots: i => (shallowReadonly(i.slots) ),
$refs: i => (shallowReadonly(i.refs) ),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (resolveMergedOptions(i) ),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy),
$watch: i => (instanceWatch.bind(i) )
});
而实例的emit在哪赋值的呢?
createComponentInstance方法中: instance.emit = emit.bind(null, instance);
看下emit的实现:
*/
// 打个断点 调用依次$emit就可以清晰看明白下面的逻辑了
function emit(instance, event, ...rawArgs) {
const props = instance.vnode.props || EMPTY_OBJ;
{
// emitsOptions 放的是 在子组件声明的emits选项
// propsOptions 放的是 在子组件声明的props选项
const { emitsOptions, propsOptions: [propsOptions] } = instance;
// 校验用户要触发的事件名字要在传入的参数范围内
if (emitsOptions) {
if (!(event in emitsOptions)) {
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "${toHandlerKey(event)}" prop.`);
}
}
else {
// 对应文档中描述的 事件验证功能
const validator = emitsOptions[event];
if (isFunction(validator)) {
const isValid = validator(...rawArgs);
if (!isValid) {
warn(`Invalid event arguments: event validation failed for event "${event}".`);
}
}
}
}
}
// emit调用的用户参数
let args = rawArgs;
const isModelListener = event.startsWith('update:');
// for v-model update:xxx events, apply modifiers on args
const modelArg = isModelListener && event.slice(7);
// v-model内置的2个处理方法 格式化参数
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 => a.trim());
}
else if (number) {
args = rawArgs.map(toNumber);
}
}
{
// 忽略
devtoolsComponentEmit(instance, event, args);
}
{
// 事件名字格式 some-event toHandlerKey: => onChange 之类的格式
const lowerCaseEvent = event.toLowerCase();
if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {
warn(`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(instance, instance.type)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`);
}
}
// convert handler name to camelCase. See issue #2249
let handlerName = toHandlerKey(camelize(event));
// 我们在使用组件的时候按照 : @some-event="parent-cb" 那在子组件的prop就会得到对应的 {someEvent: parent-cb(已绑定好父组件的this) }这样的属性
let handler = props[handlerName];
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && isModelListener) {
handlerName = toHandlerKey(hyphenate(event));
handler = props[handlerName];
}
// 执行函数即可
if (handler) {
callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args);
}
// once类型的缓存操作 只执行一次
const onceHandler = props[handlerName + `Once`];
if (onceHandler) {
if (!instance.emitted) {
(instance.emitted = {})[handlerName] = true;
}
else if (instance.emitted[handlerName]) {
return;
}
callWithAsyncErrorHandling(onceHandler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args);
}
}
// 上面的函数用到的 emitsOptions 就是被下面的函数解析出来的
function normalizeEmitsOptions(comp, appContext, asMixin = false) {
if (!appContext.deopt && comp.__emits !== undefined) {
return comp.__emits;
}
const raw = comp.emits;
let normalized = {};
// apply mixin/extends props
let hasExtends = false;
if (!isFunction(comp)) {
const extendEmits = (raw) => {
hasExtends = true;
extend(normalized, normalizeEmitsOptions(raw, appContext, true));
};
// 合并minxins中的emits
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendEmits);
}
// 合并extends选项
if (comp.extends) {
extendEmits(comp.extends);
}
if (comp.mixins) {
comp.mixins.forEach(extendEmits);
}
}
if (!raw && !hasExtends) {
return (comp.__emits = null);
}
if (isArray(raw)) {
// 当前组件的emits选项
raw.forEach(key => (normalized[key] = null));
}
else {
// 当前组件的emits选项
extend(normalized, raw);
}
return (comp.__emits = normalized);
}
// 总结一下:在子组件内部调用 $emit 其实就是触发props中的函数,这个函数的this早就在父组件的创建过程中绑定好this了,作为一个属性被传递给了子组件,子组件直接调用即可。
总结:组件实例上的 emit 方法其实就是调用props中从父组件传进来的一个箭头函数。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。