wip: filter emits

This commit is contained in:
Evan You 2024-12-03 17:20:07 +08:00
parent 9d89d7ab27
commit 4ea66770be
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
5 changed files with 81 additions and 43 deletions

View File

@ -282,9 +282,13 @@ export function normalizeEmitsOptions(
return normalized return normalized
} }
// Check if an incoming prop key is a declared emit event listener. /**
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are * Check if an incoming prop key is a declared emit event listener.
// both considered matched listeners. * e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
* both considered matched listeners.
*
* @internal for vapor only
*/
export function isEmitListener( export function isEmitListener(
options: ObjectEmitsOptions | null, options: ObjectEmitsOptions | null,
key: string, key: string,

View File

@ -488,4 +488,5 @@ export const DeprecationTypes = (
// change without notice between versions. User code should never rely on them. // change without notice between versions. User code should never rely on them.
export { baseNormalizePropsOptions, resolvePropValue } from './componentProps' export { baseNormalizePropsOptions, resolvePropValue } from './componentProps'
export { isEmitListener } from './componentEmits'
export { type SchedulerJob, queueJob } from './scheduler' export { type SchedulerJob, queueJob } from './scheduler'

View File

@ -3,6 +3,7 @@ import {
EffectScope, EffectScope,
type EmitsOptions, type EmitsOptions,
type NormalizedPropsOptions, type NormalizedPropsOptions,
type ObjectEmitsOptions,
} from '@vue/runtime-core' } from '@vue/runtime-core'
import type { Block } from '../block' import type { Block } from '../block'
import type { Data } from '@vue/runtime-shared' import type { Data } from '@vue/runtime-shared'
@ -44,6 +45,7 @@ export interface ObjectComponent
interface SharedInternalOptions { interface SharedInternalOptions {
__propsOptions?: NormalizedPropsOptions __propsOptions?: NormalizedPropsOptions
__propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>] __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
__emitsOptions?: ObjectEmitsOptions
} }
// Note: can't mark this whole interface internal because some public interfaces // Note: can't mark this whole interface internal because some public interfaces

View File

@ -0,0 +1,26 @@
import type { ObjectEmitsOptions } from '@vue/runtime-core'
import type { Component } from './component'
import { isArray } from '@vue/shared'
/**
* The logic from core isn't too reusable so it's better to duplicate here
*/
export function normalizeEmitsOptions(
comp: Component,
): ObjectEmitsOptions | null {
const cached = comp.__emitsOptions
if (cached) return cached
const raw = comp.emits
if (!raw) return null
let normalized: ObjectEmitsOptions
if (isArray(raw)) {
normalized = {}
for (const key in raw) normalized[key] = null
} else {
normalized = raw
}
return (comp.__emitsOptions = normalized)
}

View File

@ -3,8 +3,10 @@ import type { Component, ComponentInstance } from './component'
import { import {
type NormalizedPropsOptions, type NormalizedPropsOptions,
baseNormalizePropsOptions, baseNormalizePropsOptions,
isEmitListener,
resolvePropValue, resolvePropValue,
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { normalizeEmitsOptions } from './componentEmits'
export interface RawProps { export interface RawProps {
[key: string]: PropSource [key: string]: PropSource
@ -23,7 +25,7 @@ export function initStaticProps(
let hasAttrs = false let hasAttrs = false
const { props, attrs } = instance const { props, attrs } = instance
const [propsOptions, needCastKeys] = normalizePropsOptions(comp) const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
// TODO emits filtering const emitsOptions = normalizeEmitsOptions(comp)
for (const key in rawProps) { for (const key in rawProps) {
const normalizedKey = camelize(key) const normalizedKey = camelize(key)
const needCast = needCastKeys && needCastKeys.includes(normalizedKey) const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
@ -54,7 +56,7 @@ export function initStaticProps(
) )
: source : source
} }
} else { } else if (!isEmitListener(emitsOptions, key)) {
if (isFunction(source)) { if (isFunction(source)) {
Object.defineProperty(attrs, key, { Object.defineProperty(attrs, key, {
enumerable: true, enumerable: true,
@ -102,44 +104,48 @@ export function getDynamicPropsHandlers(
} }
let normalizedKeys: string[] | undefined let normalizedKeys: string[] | undefined
const propsOptions = normalizePropsOptions(comp)[0]! const propsOptions = normalizePropsOptions(comp)[0]!
const isProp = (key: string | symbol) => hasOwn(propsOptions, key) const emitsOptions = normalizeEmitsOptions(comp)
const isProp = (key: string) => hasOwn(propsOptions, key)
const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => { const getProp = (target: RawProps, key: string, asProp: boolean) => {
if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) { if (key === '$') return
const castProp = (value: any, isAbsent = false) => if (asProp) {
asProp if (!isProp(key)) return
? resolvePropValue( } else if (isProp(key) || isEmitListener(emitsOptions, key)) {
propsOptions, return
key as string, }
value, const castProp = (value: any, isAbsent = false) =>
instance, asProp
resolveDefault, ? resolvePropValue(
isAbsent, propsOptions,
) key as string,
: value value,
instance,
resolveDefault,
isAbsent,
)
: value
if (key in target) { if (key in target) {
// TODO default value, casting, etc. return castProp(resolveSource(target[key as string]))
return castProp(resolveSource(target[key as string])) }
} if (target.$) {
if (target.$) { let i = target.$.length
let i = target.$.length let source
let source while (i--) {
while (i--) { source = resolveSource(target.$[i])
source = resolveSource(target.$[i]) if (hasOwn(source, key)) {
if (hasOwn(source, key)) { return castProp(source[key])
return castProp(source[key])
}
} }
} }
return castProp(undefined, true)
} }
return castProp(undefined, true)
} }
const propsHandlers = { const propsHandlers = {
get: (target, key) => getProp(target, key, true), get: (target, key: string) => getProp(target, key, true),
has: (_, key) => isProp(key), has: (_, key: string) => isProp(key),
getOwnPropertyDescriptor(target, key) { getOwnPropertyDescriptor(target, key: string) {
if (isProp(key)) { if (isProp(key)) {
return { return {
configurable: true, configurable: true,
@ -154,8 +160,9 @@ export function getDynamicPropsHandlers(
deleteProperty: NO, deleteProperty: NO,
} satisfies ProxyHandler<RawProps> } satisfies ProxyHandler<RawProps>
const hasAttr = (target: RawProps, key: string | symbol) => { const hasAttr = (target: RawProps, key: string) => {
if (key === '$' || isProp(key)) return false if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
return false
if (hasOwn(target, key)) return true if (hasOwn(target, key)) return true
if (target.$) { if (target.$) {
let i = target.$.length let i = target.$.length
@ -169,9 +176,9 @@ export function getDynamicPropsHandlers(
} }
const attrsHandlers = { const attrsHandlers = {
get: (target, key) => getProp(target, key, false), get: (target, key: string) => getProp(target, key, false),
has: hasAttr, has: hasAttr,
getOwnPropertyDescriptor(target, key) { getOwnPropertyDescriptor(target, key: string) {
if (hasAttr(target, key)) { if (hasAttr(target, key)) {
return { return {
configurable: true, configurable: true,
@ -181,16 +188,14 @@ export function getDynamicPropsHandlers(
} }
}, },
ownKeys(target) { ownKeys(target) {
const staticKeys = Object.keys(target).filter( const staticKeys = Object.keys(target)
key => key !== '$' && !isProp(key),
)
if (target.$) { if (target.$) {
let i = target.$.length let i = target.$.length
while (i--) { while (i--) {
staticKeys.push(...Object.keys(resolveSource(target.$[i]))) staticKeys.push(...Object.keys(resolveSource(target.$[i])))
} }
} }
return staticKeys return staticKeys.filter(key => hasAttr(target, key))
}, },
set: NO, set: NO,
deleteProperty: NO, deleteProperty: NO,