-
Notifications
You must be signed in to change notification settings - Fork 12.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
More accurate typing of Object.assign and React component setState() #6613
Comments
@RyanCavanaugh what do you think? |
Let's say for a moment we had a type operator |
I think that your description is more concise and clear, and I think |
A more general purpose alternative would be to allow 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 |
@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 @RyanCavanaugh suggested - that way I can declare new types and declare variables of optionalized types. |
The problem with 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 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 |
Somewhere here in issues I see this proposal for similar case:
Isn't it the similar thing? |
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. |
Are type operators even on the table, or maybe tracked in a separate issue? |
type operators keep coming up in different context. They are not totally dismissed, just not in the short term. the concern is the effort needed to add one of these, as it goes deep in the type system. |
@mhegazy you're missing the point - all-optionals is NOT a workaround, you haven't understood the problem at all. Even if you specify all optionals, the compiler will not accept the code and you will have to cast to any. This affects everybody using React and subclassing. Even if this is not on the near term roadmap please keep this issue open. |
@RyanCavanaugh please help |
@rogierschouten with what specifically? |
Another possible solution could be allowing having member-level constraints for types defined in containing interface/class #1290 class ReactComponent<S extends {}> {
protected _state: S;
/**
* This method accepts any supertype of S
*/
protected setState<T, S extends T>(newState: T): void {
}
} Currently it introduces new |
Yes, I thought of some sort of variation of F-bounded polymorphism (#5949) might help solve this problem, I think the problem here is how to set a type that cannot be resolved, even recursively, until the method's type is resolved. S's type is essentially a moving target. |
Agree with @Igorbek. I thought F-bounded polymorphism would just work in this case without the need for new keywords and type operators. |
@joewood Your function code doesn't work for all cases either. If you had a legitimate optional property in your State, then you wouldn't be able to update it ( { a?: number} is not a valid subtype of { a: number } ). |
@AlexGalays true, but wouldn't it more correct to say that the state was |
@joewood Nice! I was still using void as the type containing undefined, looks like the new undefined type works better. |
Your example doesn't work with other combinations, like unions False positive interface IState {
readonly foo:number;
readonly bar: 'aa' | 'bb';
};
const s : IState = { foo:1, bar: "aa"};
// Compiler is happy, but ideally shouldn't
merge(s, { bar: 'thisShouldntBeAssignable'}) Or: "False" negative type A = { _isA: boolean }
type B = { _isB: boolean }
interface IState {
readonly foo:number;
readonly bar: A | B;
};
const s : IState = { foo:1, bar: { _isA: true }};
// Doesn't compile, as an union cannot be assigned to just one of its types
merge(s, { bar: { _isB: true }}) This is sad :( I really wanted to have usable deep JSON updates. |
I think there's a couple of things going on with your examples, and agree - this really isn't ideal. For your first example, I think this relates to the rule where mutable bindings are widened for string literal types. See these issues for the details on that one #9489 and #5185. I would argue that no widening should apply to a constant expression - it's hard to argue that the compiler is doing the right thing here. The second example is down to the fact that type |
I completely agree with your points. Also there might be quite a few other corner cases I didn't find yet. |
|
Thanks for the link, I would love for this to be in 2.1 |
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:
and then:
Such that the "type" I'm specifying for T will work. For example:
This is currently generating the error:
I can shift various and sundry things around, but I cannot find a combination that: 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
|
#12793 should have solved the react problem. Is there something still open here about Object.assign, or is |
I think the only issue with the current |
#10727 would fix it because type of |
relevant discussion about distinguishing |
Fixed with mapped types. Please log new issues if you find any shortcomings in this area |
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.
The text was updated successfully, but these errors were encountered: