-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Fix toObject TS issues #9014
Fix toObject TS issues #9014
Conversation
@jiayihu i think SProps was added in the constructor for this reason. I can add a property const r = new Rect({ name: 'test' }); then when i export to toObject, i want Since serializedProps is an interface, i can still use the merge property of interfaces and make that work, correct? i just need to do it outside the class declaration. instead of doing: const r = new Rect<A , B, C>({ name: 'test' }); i will do import { SerializedRectProps } from ...
interface SerializedRectProps {
// add name to all rects?
name: 'string';
}
const r = new Rect<A , C>({ name: 'test' }); Is this correct? The only difference is that i would act on all the the Rect classes since the interface merge i think is global? Please explain me as i i was a 5 years old because i m still learning TS |
@jiayihu I told you AssertKeys was just the beginning. |
IMO we don't need declaration merging nor ClassProperties for a subclass, we just pass SProps. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure omitting SProps is the right way to go.
Did you try only removing the usage of keyof this?
We had the exact same problem when trying to type set, _set
K extends keyof T = never | ||
>(propertiesToInclude: K[] = []): Pick<T, K> & SProps { | ||
toObject( | ||
propertiesToInclude: Array<keyof SerializedEllipseProps> = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes no sense
You would not pass keus of serialized props because they are already there.
I undrrstand you are thinking of declaration merging but still
let's try to flesh out why removin SProps is not a good idea so that i can follow the reasoning. |
See my initial post, I tried with a simplified type already and explained why any type parameter would break assignability of subclasses. toObject<
SProps extends SerializedITextProps = SerializedITextProps,
K extends keyof SProps = never
>(propertiesToInclude: K[] = []): SProps; In the above simplified type, both the parameters are not assignable (
You can't really expect people to continuously silence something common such as assigning a
Actually it seems, at least for now, that AssertKeys + |
Yes exactly. Note: declaration merging is done on module level. // fabric.d.ts
import { SerializedRectProps } from "fabric"
declare module "fabric" {
interface SerializedRectProps {
name: 'string';
}
}
Yes correct, that's indeed a drawback. I think that if we make You still can't do it for a single rect object on-the-fly as in your example, but it could be a good compromise. |
I need to read this again and think about it |
making interface changes at class level is not big issue. I also need to read this deeply, i m ok with a bit of simplification at the cost of neglegible drawbacks |
Guys I was thinking - toObject(propsToInclude) {
+ toObject() { Fabric barely uses it |
I'd be fine indeed with simplifying the signature as it simplifies the typing, giving also more freedom to the caller. Yesterday I found another case of this pattern: getObjects(...types: string[]): FabricObject[] This forces to do const isMyObject = (o: FabricObject): o is MyObjectType => o.type === "myObjectType"
const myObjects: MyObjectType[] = canvas.getObjects().filter(isMyObject) // Okay This perfect:
So I think a similar pattern could be followed here for |
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs 😔. Thank you for your contributions. |
This solved my problem when encountering this. |
For some obscure reasons, the current complex TS types for
toObject
makes a perfectly fine instance ofTextbox
not assignable toIText
because of incompatibility intoObject
.After simplifying the types like this, it was still not assignable but this type is easier to reason about.
Textbox
is assignable to a parameter of typeIText
if every property and method is assignable, i.e. every property and method ofTextbox
is subtype ofIText
Textbox.prototype.toObject
is subtype ofIText.prototype.toObject
if:propertiesToInclude
fromIText
is assignable to the one fromTextbox
. Notice the change of direction, this is couter-intuitive. But indeed forTextbox.prototype.toObject
to be used in place ofIText.prototype.toObject
, it means that it must be safely accept the parameters fromIText.prototype.toObject
SProps
fromTextbox
is assignable to the return type fromIText
. This is intuitive.The issue with the above simplified type is that
SProps
fromTextbox
can be different fromSProps
fromIText
, even though apparently the one from Textbox seems to extend the one in IText in the default case. E.g.The above two types are incompatible even though they seem both to extend from
SerializedTextProps
. And this issue applies both to the parameter type and the return type oftoObject
. I think this also explains why the current overcomplex typeT extends Omit<Props & TClassProperties<this>, keyof SProps>
just makes everything worse and for some reason TS seem even to just give up and say thatK extends keyof T
is juststring | number | symbol
forpropertiesToInclude
fromIText
(not shown in the above screenshot because it contained only the beginning of the huge TS error).string | number | symbol
is the default type returned fromkeyof T
whentype T = object
, so it's like as if TS just said thatT extends Omit<Props & TClassProperties<this>, keyof SProps>
forIText
is just any generic object.So my proposal is to simplify the types for
toObject
, which I don't expect to be that much called, except for serialization. So the current attempt to be overly-accurate in the TS is counter-effective. And if you want to customize the types, you can just do declaration merging, e.g. extend the interface ofSerializedTextProps
to add extra properties.The advantage now is also that, because we're not doing
SProps extends SerializedTextProps
, it's not possible to have aSProps
type fromTextbox
that is incompatible with theSProps
fromIText
. ThetoObject
method statically expects akeyof SerializedTextboxProps
, which is guaranteed to extend fromSerializedTextProps
so TS can easily verify thatSerializedTextProps -> (assignable) SerializedTextboxProps
for the parameter type andSerializedTextboxProps -> SerializedTextProps
for the return type, thusTextbox.prototype.toObject -> IText.prototype.toObject
I think this should significantly also improve type checking performance, since TS doesn't have to do all the
Omit<Props & TClassProperties<this>, keyof SProps>
computation and compare if oneSProps
is assignable to the other.