Repository navigation
More accurate typing of Object.assign and React component setState() #6613
Description
Activity
rogierschouten commented
on Jan 25, 2016 AuthorMore actionsRyan Cavanaugh (@RyanCavanaugh) what do you think?
RyanCavanaugh commented
on Jan 25, 2016 MemberMore actionsLet's say for a moment we had a type operator
optionalizethat took an object type and produced a new object type whose properties were all optional. What would the trade-offs of that be vspartoffor solving the ReactsetStateproblem?rogierschouten commented
on Jan 25, 2016 AuthorMore actionsI think that your description is more concise and clear, and I think
optionalizeis a very intuitively clear keyword. At first glance, I see no difference in usability between our proposals, do you?A more general purpose alternative would be to allow
superconstrains for type parameters:class ReactComponent<S extends {}> { protected _state: S; /** * This method accepts any supertype of S */ protected setState<T super S>(newState: T): void { } }
The requirement implied by
T super Sis thatTmust be a type to whichSis assignable.Reacted by Stepan Mikhailiuk, Jeff Swenson, Herrington Darkholme, Marc O'Morain, Jamie Humphries, Ilia Tsvetkov and Christophe Bioccarogierschouten commented
on Jan 27, 2016 AuthorMore actionsAnders Hejlsberg (@ahejlsberg) that would work too, but I would avoid the word 'super' because one would not know whether you mean supertype or superclass or super-something-else. Maybe use 'S assignable T'?
However I've found a number of use cases for a type operator 'optionalize' like Ryan Cavanaugh (@RyanCavanaugh) suggested - that way I can declare new types and declare variables of optionalized types.
RyanCavanaugh commented
on Jan 27, 2016 MemberMore actionsThe problem with
superhere is that it's inaccurate in several ways:declare function setState<T super { s1: { x: Derived; y: number; } }>(s: T); setState( { x1: 32 }); // No error, but wrong setState( { s1: { y: 42 } }); // No error, but wrong setState( { s1: { x: new Base(), y: 32 } }); // No error, but wrong
I also want to say that this use case is important to other libraries as well (and I think the title is a bit misleading, really what the intent of this is the way to operate on a type to make all its members optional).
I think it is a common pattern to pass an object literal to a constructor (or factory) function that can be any of the public properties of the instance class, which is then mixed in upon construction. I ran into this issue when trying to type Dojo 1 Dijits. The only option, if I wanted to type the constructor functions, is to create a wholly separate interface which has all optional members.
I agree
supermight give the wrong semantic impression. I suspectoptionalizeoroptionalwould be more semantically clear:class A { foo: string; bar(): string { return 'bar'; }; constructor<O optionalize this>(options: O) { }; } type OptionalA = optionalize A; /*or*/ class B { foo: string; bar(): string { return 'bar'; }; constructor(options: $Optionalize<this>) { }; } type OptionalB = $Optionalize<B>;
Whatever we do here also applies to
Object.assignthat was introduced in ES2015.- changed the title
[-]Proposal for supporting React component setState() typing[/-][+]More accurate typing of Object.assign and React component setState()[/+]on Jan 28, 2016 - addedSuggestionAn idea for TypeScriptAn idea for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensus
on Jan 28, 2016 Somewhere here in issues I see this proposal for similar case:
function assign<R extends A,B,C>(a:A, b:B, c:C):RIsn't it the similar thing?
rogierschouten commented
on Jan 28, 2016 AuthorMore actionsArtur Eshenbrener (@Strate) I don't think so, see the first use case of post #6218. The first use case was solved like you said but the setState() use case wasn't.
We'd need type operators to solve this, which is a huge work item; Workaround of specifying all-optionals in state definition, which isn't too bad.
20 remaining items
I can't follow the entire discussion here, but this seems to be related to the reason why I can't find a construct that allows me to say:
class Message { public static TypeID: string; public TypeID: string; public Sequence: number; constructor() { } }and then:
public receive<T extends Message>(rcvType: T, handler: (msg: T) => void): void {...}Such that the "type" I'm specifying for T will work. For example:
class SomeMessage extends Message { public static TypeID = "Messages.Commands.SomeMessage"; public TypeID = "Messages.Commands.SomeMessage"; constructor(public Payload: string){ super(); } }This is currently generating the error:
receive(SomeMessage, (msg: SomeMessage) => { console.log(msg.Payload); }); The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly. Type argument candidate 'typeof SomeMessage' is not a valid type argument because it is not a supertype of candidate 'SomeMessage'. Property 'prototype' is missing in type 'SomeMessage'.I can shift various and sundry things around, but I cannot find a combination that:
a) doesn't complain
b) explicitly ties the two types such that they must be the same type (I can specify things in a way that two different values for rcvType and handler/msg will work, where the intent is that they are the same)FWIW, rcvType is important because it's used internally to set things up. I'd also love if I could just not specify rcvType (but I realize type erasure precludes that).
Looks like this has been resolved with #12114.
I'm still trying to digest this PR...
On Wed, Nov 16, 2016 at 6:17 PM, Dibyo Majumdar notifications@github.com
wrote:Looks like this has been resolved with #12114
#12114.—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#6613 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AC-vl_bEXaisDZ_JaeaRwVSdpr1lC7CPks5q-7kcgaJpZM4HL6Cd
.#12793 should have solved the react problem. Is there something still open here about Object.assign, or is
Pick<...>good enough for it too?Object.assigndon't wantPick<>as you can merge any kind of objects into any kind of object.
If you want to update an object with only objects of the same shape, then you will want to code something that looks a lot like the solution in #12793 indeed.I think the only issue with the current
Object.assigntype definition is when the right hand side object has a key with a different type. Instead of it completely replacing the initial type (like in the implementation) the result becomes the union of both types, which sometimes don't even make sense (e.g number & string)#10727 would fix it because type of
Obejct.assign(a, b)is exactly type of{...a, ...b}.relevant discussion about distinguishing
missingandundefinedis tracked in #16524- addedCommittedThe team has roadmapped this issueThe team has roadmapped this issueand removedIn DiscussionNot yet reached consensusNot yet reached consensus
on Aug 15, 2017 RyanCavanaugh commented
on Aug 15, 2017 MemberMore actionsFixed with mapped types. Please log new issues if you find any shortcomings in this area
- locked and limited conversation to collaborators
on Jun 19, 2018
This is a proposal for solving the second use case mentioned in Issue #6218
Problem
The compiler cannot know that the setState() method will accept any super-type of S, i.e. an object with any subset of the members of S.
Proposal
Add a keyword to type setState() properly. To avoid confusion, I chose partof instead of e.g. 'supertype of' (see also initial confusion in #6218). For me, 'partof' conveys that I can give the method any object with a subset of the members of S.
Properties of partof
class ReactComponent<S extends { foo: number; }>, one is able to callsetState({ foo: 3})andsetState({})but notsetState({ bar: 3 })inside the class definition.Comments more than welcome, I'm not a compiler expert. This is just to get the discussion going. If you think there already exists a way of typing this, please check with the original issue for a couple of failed examples.