-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Make defining a data class easier #38442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The most concise way I found to define such a class, is to use a function to generate a class. In the function the class uses function autoImplements<T>() {
return class {
constructor(data: T) {
Object.assign(this, data)
}
} as new (data: T) => T
}
interface Data {
prop : number;
}
class SomeData extends autoImplements<Data>() {
m() {
this.prop
}
}
let s = new SomeData({
prop: 2
})
s.prop |
@dragomirtitian This is actually quite good! The only thing it doesn't deal with is the visibility modifier, default value, and not being able to use decorator. It's a great start. If this issue didn't end up go anywhere, I'll definitely use this approach. |
@zen0wu you can add a defaults parameter to the function. With regard to visibility, I'm not sure exactly what you would want to do, but you can't make just some of the properties public. And decorators could be an issue. But you can probably just redefine the fields you want decorated |
@dragomirtitian re default parameters - I guess we could, with a second generic denoting a subset of T, something like Playground Link. Some rough edges like overwriting default values is a bit weird, probably solvable. By visibility, compared to parameter properties, we can freely define the visibility of each individual parameters. For decorators, this is mostly from the typeorm usecase, so basically every field will be decorated :) |
named parameters to the constructor (like in Python) would solve this very elegantly |
@bogdanionitabp could you elaborate? Are you proposing a change or there’s already a solution in your mind? |
@bogdanionitabp there should only be 1 parameter to the constructor. otherwise you would always have to rewrite all the parameters for the super call. also you might want to define the shape of the parameters somewhere in a separate object. can't be done if you have multiple parameters. C# does a lot of thing better then JS, but named params (C# has them) are a bit of a pain point. |
#38442 (comment) @dragomirtitian Nice one, this is a little bit better, it avoids to create a new class every time: const Struct = class Struct {
constructor(data: any) {
Object.assign(this, data);
}
} as new <T>(data: T) => T;
class Hello extends Struct<{
x: string;
}> {}
const hello = new Hello({ x: "world" });
console.log(hello.x); // world |
Search Terms
Data class, Property parameters
Suggestion
Data class just means a class, with certain forms of pattern of how it is defined (required), and some predefined methods (optional, like hashCode, equals...).
Basically we want to achieve the following, very simple goal.
Goals:
DataClass
generic classThe most immediate pain point is, due to lack of keyword arguments in JS, defining a data class is very awkward. There are a few ways to mimic a data class, but they all come with different awkwardness.
The most correct and cumbersome definition
That's a lot of boilerplate! Each field is written 4 times! This already feels like Java.
Property parameters
Most ideal when defining the class, but don't really satisfy requirement 1.
Define constructor argument based on class info
The issues with this approach is:
strict
, have to add!
to every fieldPartial
cannot ensure the most correct type infoAnother try to improve the type safety around missing fields:
The issue is,
Shape
will include all the methods and stuff that's not really part of the constructor we want.Interface + Class
Use Cases
Data class is a super common use case, and is already natively supported in other languages like in Kotlin, Python, Scala and etc.
That being said, I wouldn't doubt this would have numerous use cases and it would make things so much easier, especially in large code base.
Use case I've personally seen:
Thinking about React's component and their props, it's actually the same pattern, compared to the interface+class approach above. But React's choice is to have to append
.props
on every field access. Reasonable choice given there's alsothis.state
, but it could be better.Possible Solutions
There's many ways we could make this better. Just listing some of the possibility. I'm honestly not sure which one is the best. But the idea here is to have a design that have no runtime assumption or changing how JS is emitted.
Non-solutions:
data class
(as in kotlin/scala) modifier on class definition. This would have runtime implications, changing how classes code are emitted in JSExtend property parameters to support objects
Looks legit and scoped, since. this only changes the typing of constructor.
But this might put limitation on constructor arguments: For example, maybe we only allow one constructor argument to have this behavior, or not.
Have a way to mark fields as needed in constructor
Similiarly to
readonly
:Asserts in constructor
This has to use the Props pattern. Given this is not really adding return types to constructor, but merely an assert type. Definitely feels harder to implement because it might interfere with flow control analysis. But the nice thing is,
Checklist
I'm quite sure we can find a way to satisfy all the guidelines here.
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: