#How type class decorators?

54 messages · Page 1 of 1 (latest)

buoyant canopy
#

Can someone explain how to create a class decorator that modifies the decorated class such that TS doesn't yell at me?

I tried creating a simple decorator that simply adds one property bar on the prototype. The code works at runtime (click Run on the playground), but TS is not happy with the code. I'm using Object.defineProperties because my real use-case creates the name dynamically; this is a simplified example of the type woes. If it matters, I'm not making an old TS experimental decorator, but an ES decorator. As far as I can tell, my implementation is compliant with the TC39 proposal. The problem is, that I don't know how to make it compliant with TS and I can't for the life of me find any documentation on how to create ES decorators in TS.

I'm getting multiple confusing errors:

  • A mixin class must have a constructor with a single rest parameter of type 'any[]'. What does this mean? I tried adding what it requested and the error didn't go away and the requested line errors too. Explicit constructors shouldn't be required anyway in a decorator.
  • Property 'bar' does not exist on type 'Foo'. Why not? It's being added in the decorator and the type is augmented to match. It exists at runtime, so what do I need to do to make TS realize this?
unreal moonBOT
#
okku#0

Preview:```ts
class Base {
static staticProp = 13
instanceProp = 42
}

function addBar<ClassType extends typeof Base>(
Class: ClassType
) {
return class extends Class {
constr
...```

vagrant cloud
#

if you're still stuck after reading that let me know. then i'll take a closer look and try to provide specific advice, but if you're able to solve things yourself that's even better

buoyant canopy
#

It explains my second bullet point: my use case is basically not supported. I was able to find a related issue as well. However, it doesn't help with the first bullet point 🙁

vagrant cloud
#

you need to add the ...args: any[] construct signature to Base:

unreal moonBOT
#
mkantor#0

Preview:```ts
class Base {
static staticProp = 13
instanceProp = 42
constructor(...args: any[]) {}
}

function addBar<ClassType extends typeof Base>(
Class: ClassType
) {
return clas
...```

vagrant cloud
#

and the one in the implementation isn't necessary

buoyant canopy
#

Ah I see, I put it in the wrong place

vagrant cloud
#

also i don't think that type assertion is doing anything useful

#

i guess that's more related to your second issue, though

buoyant canopy
#

Yup

#

I was hoping it would do something, but TS just straight up doesn't support mixin decorators it seems

vagrant cloud
#

i think it's just changing the shape of the class that isn't supported

buoyant canopy
#

I'm honestly kind of confused how it's possible that decorators have been such a popular and highly requested feature according to the people implementing these, when they're so useless. Every time I think I've finally come up with a use-case, I'm met with my use-case not being supported

vagrant cloud
#

you're preaching to the choir... decorators have always seemed pretty useless to me (even in plain JS)

#

well not useless, just a way to obscure what's really happening without much benefit for the type of code i write

#

i'd much rather see explicit function calls than sneaky implicit ones

buoyant canopy
#

The problem I'm trying to solve is trying to avoid quintuple-defining element properties for custom elements (web compoennts api)

#

I don't want code to be that explicit

vagrant cloud
#

i haven't really used web components so haven't experienced that pain myself. can you share an example of the "bad" kind of code that you want to avoid? i'd be surprised if there isn't some other way to abstract away the boilerplate

#

i know there are a few people who hang out on this server who are fans of web components, so even if i can't give you good advice perhaps one of them can chime in

buoyant canopy
#

Here's how it basically needs to be done without any sugar, assuming you want your elements to behave and respond to changes similarly to native ones. I'm using reactive() which is a signal for tracking reactivity. However, don't let that distract you, that might as well be a private field with the same value instead, if you didn't want reactivity. ```ts
declare const reactive: <T>(input: T) => ({
value: T,
});

class Comp extends HTMLElement {
protected props = {
foo: reactive("default value"),
};

static get observedAttributes () {
    return ["foo"];
}

attributeChangedCallback (
    name: keyof typeof this.props,
    _oldValue: string,
    newValue: string,
) {
    if (!(name in this.props)) return;
    this.props[name].value = newValue;
}

get foo () {
    return this.props.foo.value;
}

set foo (newValue: string) {
    this.props.foo.value = newValue;
}

//finally able to use the value
connectedCallback () {
    console.log(this.props.foo.value);
}

}
Here's what I was hoping to boil it down to: ts
@prop({
foo: "default value",
})
class Comp extends HTMLElement {
connectedCallback () {
console.log(this.props.foo.value);
}
}

#

But I'm thinking that I will explore something like this next: ts class Comp extends Compoennt({ foo: "default value", }) { connectedCallback () { console.log(this.props.foo.value); } } I.e. Component becomes a function that dynamically creates values. It's less desirable, because it's less intuitive to have to call the extended class as a function even when there's no props class Comp extends Component() {}

vagrant cloud
#

that sort of thing was what immediately came to mind for me too

#

is the getter/setter necessary for the web component? or just a niceity so you can write blah.foo = ... instead of blah.setFoo(...)?

#

i could imagine it's needed for HTML attribute syntax in the custom element or something

#

i suppose i could just look this up instead of asking you 😂

#

after skimming this i suspect it's just a niceity, though i didn't thoroughly read the page so could have missed something

#

but maybe you could live without those and instead use setAttribute/getAttribute to further reduce boilerplate

buoyant canopy
#

So it must be get&set pair in order to respond to the property changing

vagrant cloud
#

yeah, but those sorts of behaviors have custom logic per specific element type, so you'd need to write a specific method for it anyway. i was focused on the basic case where there's no additional logic

#

like IIRC input's value does custom type coercion and maybe deals with '' specially, etc

vagrant cloud
buoyant canopy
#

Yeah, this is the basic case. I was thinking about something like this for more complex: ```ts
class Comp extends Compoennt({
foo: {
value: 42,
attributeParser: Number,
}
}) {
connectedCallback () {
console.log(this.props.foo.value);
}
}

#

But even in the basic case it should allow use of both attributes and properties, just like native elements (think <input type=text>)

buoyant canopy
vagrant cloud
#

i'm out of my element (heh) here so pardon the ignorance, but what does that mean? i thought react's JSX transform emitted code like React.createElement(elementName, attributes, children)

#

i guess i don't really know what the intersection of custom elements & react/JSX looks like. are they intrinsic elements from react's perspective?

buoyant canopy
#

replace attributes with properties and you've got it

#

Remember how React was using things like htmlFor instead of for and className instead of class? It's because these are the property equivalents for their JS interface

vagrant cloud
#

right, the part i don't get is when you said "frameworks like React deal with properties exclusively". do you mean that this code is going through createElement and inside createElement it's just doing the equivalent of element[yourPropertyName] = yourPropertyValue?

buoyant canopy
#
<input type="number" className="foo" valueAsNumber={42} />
``` in JSX is roughly equivalent to ```ts
const el = Object.assign(document.createElement("input"), {
    type: "number"
    className: "foo",
    valueAsNumber: 42,
});
vagrant cloud
#

(sorry that i'm sidetracking your thread here btw, feel free to tell me to RTFM and we can shift focus back to your question)

buoyant canopy
#

Idm it, it's a good chat and I'm most likely not gonna get a better answer anyway

#

!resolved

vagrant cloud
buoyant canopy
#

But in contrast, someone using HTML only would be really upset if attributes weren't working 🙂

buoyant canopy
buoyant canopy
#

I feel like I tried this before but had forgotten all about it

vagrant cloud
buoyant canopy
#

After struggling for 1½ hours to try to reproduce it, I found the issue myself, sorry to have bothered you 😅