From 4772d12c6348b1f081fceb03f0a69f2049366f53 Mon Sep 17 00:00:00 2001 From: mantou132 <709922234@qq.com> Date: Thu, 3 Oct 2024 01:50:27 +0800 Subject: [PATCH] [gem] Fixed #204 --- packages/gem/src/lib/decorators.ts | 74 +++++++++++-------- .../gem/src/test/gem-element/advance.test.ts | 8 ++ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/packages/gem/src/lib/decorators.ts b/packages/gem/src/lib/decorators.ts index a9844619..e5f771fe 100644 --- a/packages/gem/src/lib/decorators.ts +++ b/packages/gem/src/lib/decorators.ts @@ -7,7 +7,7 @@ import * as decoratorsExports from './decorators'; import * as storeExports from './store'; import * as versionExports from './version'; -type GemElementPrototype = GemElement; +type GemElementPrototype = GemElement & { '': never }; type StaticField = Exclude; const { deleteProperty, getOwnPropertyDescriptor, defineProperty } = Reflect; @@ -18,10 +18,8 @@ function pushStaticField(context: ClassFieldDecoratorContext | ClassDecoratorCon const metadata = context.metadata as Metadata; if (!getOwnPropertyDescriptor(metadata, field)) { // 继承基类 - const current = new Set(metadata[field]); - current.delete(member); defineProperty(metadata, field, { - value: [...current], + value: metadata[field]?.filter((e) => e !== member) || [], }); } @@ -111,21 +109,35 @@ function defineProp( }); } +// https://github.com/tc39/proposal-decorators/issues/517 +const targetCache = new WeakMap(); +function getDecoratorTarget(element: GemElement, { metadata }: ClassFieldDecoratorContext) { + const target = targetCache.get(metadata); + if (target) return target; + let result = element as GemElementPrototype; + do { + result = getPrototypeOf(result); + } while (result.constructor[Symbol.metadata] !== metadata); + targetCache.set(metadata, result); + return result; +} + type AttrType = BooleanConstructor | NumberConstructor | StringConstructor; function decoratorAttr(context: ClassFieldDecoratorContext, attrType: AttrType) { const prop = context.name as string; const attr = camelToKebabCase(prop); context.addInitializer(function (this: T) { - const target = getPrototypeOf(this); + const target = getDecoratorTarget(this, context); if (!target.hasOwnProperty(prop)) { pushStaticField(context, 'observedAttributes', attr); // 没有 observe 的效果 defineProp(target, prop, { attr, attrType }); - // 记录观察的 attribute - const attrMap = observedTargetAttributes.get(target) || new Map(); - attrMap.set(attr, prop); - observedTargetAttributes.set(target, attrMap); } clearField(this, prop); + // 记录观察的 attribute, 用于 hack 的 setAttribute + const proto = getPrototypeOf(this); + const attrMap = observedTargetAttributes.get(proto) || new Map(); + attrMap.set(attr, prop); + observedTargetAttributes.set(proto, attrMap); }); // 延时定义的元素需要继承原实例属性值 return function (this: any, initValue: any) { @@ -166,7 +178,7 @@ export function numattribute(_: undefined, context: ClassF export function property(_: undefined, context: ClassFieldDecoratorContext) { const prop = context.name as string; context.addInitializer(function (this: T) { - const target = getPrototypeOf(this); + const target = getDecoratorTarget(this, context); if (!target.hasOwnProperty(prop)) { pushStaticField(context, 'observedProperties', prop); defineProp(target, prop); @@ -214,14 +226,15 @@ export function memo( } /** - * 依赖 `GemElement.effect` - * 方法执行在字段前面 + * - 依赖 `GemElement.effect` + * - 方法执行在字段前面 + * - 如果没有 oldValue,在为首次执行 * * For example * ```ts * class App extends GemElement { * @effect(() => []) - * #fetchData() { + * #fetchData(newValue, oldValue) { * console.log('fetch') * } * } @@ -328,11 +341,11 @@ function defineCSSState(target: GemElementPrototype, prop: string, stateStr: str * ``` */ export function state(_: undefined, context: ClassFieldDecoratorContext) { + const prop = context.name as string; + const attr = camelToKebabCase(prop); context.addInitializer(function (this: T) { - const target = getPrototypeOf(this); - const prop = context.name as string; + const target = getDecoratorTarget(this, context); if (!target.hasOwnProperty(prop)) { - const attr = camelToKebabCase(prop); pushStaticField(context, 'definedCSSStates', attr); defineCSSState(target, prop, attr); } @@ -408,28 +421,25 @@ export type Emitter = (detail?: T, options?: Omit(_: undefined, context: ClassFieldDecoratorContext) { - context.addInitializer(function (this: T) { - defineEmitter(context, this, context.name as string); - }); + defineEmitter(context); } export function globalemitter(_: undefined, context: ClassFieldDecoratorContext) { - context.addInitializer(function (this: T) { - defineEmitter(context, this, context.name as string, { bubbles: true, composed: true }); - }); + defineEmitter(context, { bubbles: true, composed: true }); } -function defineEmitter( +function defineEmitter( context: ClassFieldDecoratorContext, - t: GemElement, - prop: string, eventOptions?: Omit, 'detail'>, ) { - const target = getPrototypeOf(t); - if (!target.hasOwnProperty(prop)) { - const event = camelToKebabCase(prop); - pushStaticField(context, 'definedEvents', event); - defineProp(target, prop, { event, eventOptions }); - } - clearField(t, prop); + const prop = context.name as string; + const event = camelToKebabCase(prop); + context.addInitializer(function (this: T) { + const target = getDecoratorTarget(this, context); + if (!target.hasOwnProperty(prop)) { + pushStaticField(context, 'definedEvents', event); + defineProp(target, prop, { event, eventOptions }); + } + clearField(this, prop); + }); } /** diff --git a/packages/gem/src/test/gem-element/advance.test.ts b/packages/gem/src/test/gem-element/advance.test.ts index c934dc9a..7ea2ca66 100644 --- a/packages/gem/src/test/gem-element/advance.test.ts +++ b/packages/gem/src/test/gem-element/advance.test.ts @@ -304,12 +304,20 @@ class InheritGem extends I { @attribute appTitle = '1'; @attribute appTitle2 = '2'; } + +@customElement('inherit-gem1') +class InheritGem1 extends I {} + describe('gem element 继承', () => { it('静态字段继承', async () => { new I(); new InheritGem(); // 触发装饰器自定义初始化函数 const metadata: Metadata = Reflect.get(InheritGem, Symbol.metadata); expect(metadata.observedAttributes).to.eql(['app-title', 'app-title2']); + + new InheritGem1(); // 触发装饰器自定义初始化函数 + const metadata1: Metadata = Reflect.get(InheritGem1, Symbol.metadata); + expect(metadata1.observedAttributes).to.eql(['app-title']); }); it('attr/prop/emitter 继承', async () => { const name = window.name;