-
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
Partial Types (Optionalized Properties for Existing Types) #4889
Comments
I brought this up recently with @mhegazy, and it can really come in handy, especially
interface Props { /*...*/ };
class Foo implements Props {
constructor(opts: partial Props) { /*...*/}
}
let NewThing: NewMembers & (partial ThingImMixingInWith) = { /*completionHere*/ And I'm sure in other scenarios. How we choose to go about making this available is a separate matter. |
Just for bookkeeping, it's the same as $Shape from #2710. |
@s-panferov Does |
And type guard against anything that isn't actually in the interface. I would suspect Why wouldn't the behaviour always just be like |
@kitsonk I was thinking of |
This would work nicely:
|
This would make typings for lodash/underscore pretty awesome. Would also be usefull in cases where you get a partial model as a param on the constructor to build the actual model. This way we wouldn't need to create a sibling interface just to hold the same properties with different optionality. class Person {
name: string;
surname: string;
birthday: Date;
numberOfThings: number;
constructor(initialValues: partial Person) {
this.name = initialValues.name;
this.surname = initialValues.surname;
this.birthday = initialValues.birthday;
this.numberOfThings = initialValues.numberOfThings;
}
}
new Person({name: 'Fred', surname: 'Galvão', numberOfThings: 2}); |
Yes, this would be extremely handy! Another good use case is React "higher-order" components - I want to implement a component that acts just like another, "lower-order" component, except it automatically calculates the value of some of its props... example time: interface IUserAvatarProps {
url: string,
size?: number
}
class UserAvatar extends React.Component<IUserAvatarProps, {}> {
//...
}
interface ISmartUserAvatarProps implements partial IUserAvatarProps {
userId: string
}
class SmartUserAvatar extends React.Component<ISmartUserAvatarProps, {avatarUrl: string}> {
render() {
return <UserAvatar url={this.state.avatarUrl} {...this.props} />;
}
componentWillMount() {
// Fetch this.state.avatarUrl from this.props.userId
}
}
// Later...
<SmartUserAvatar id="1234" size={32} /> |
Yet another use case would be backbone models which have defaults and the constructor takes a partial set of attributes which override the defaults: interface CarData { make: string, model: string }
class Car extends Backbone.Model<CarData> {
defaults: CarData = {
make: 'BMW',
model: '7'
}
}
new Car({ model: 'X5' }); |
+1 |
+1 For me, the big benefit of such functionality would be in being able to create typesafe APIs around the various libraries for immutable data structures like Of course, some basic language level functionality for implementing or expressing lenses would also be a potential way of addressing the issue. |
There is a use case for the It could be defined using partial types like this:
Now I could do this:
As all arguments are stated to be subsets of T, the call expression type could be inferred to be:
That would be assignable to the following type:
It'd be assignable because If this was possible, I could pass a variable
As The final case, would be when I state the type of T when calling. The inference algorithm could then make sure that the resulting type is what I expect, or give me an error:
For the keyword, it could be Does this make sense? |
Sorry @masbicudo if I didn't understand your example correctly but I think it can already be typed using "type parameters as constraints", see the first post in #5949 |
I've done this as a test, and I am pretty excited with the possibilities this seems to open.
Is it related? |
With the latest 1.8-beta, it's possible to get this to work when both type parameters are defined on the function. Unfortunately, I doesn't seem to solve the I.e.: declare function setState<T, S extends T>(state: S, partial_state: T) // works!
interface Component<S> {
state: S
setState<T, S extends T>(partial_state: T) // doesn't work as S is considered a new type param
} |
I have the same issue as @guncha when trying to type a cursor structure
+1 |
Agree with @masbicudo about |
@mjohnsonengr since TypeScript 1.8 /**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source The source object from which to copy properties.
*/
assign<T, U>(target: T, source: U): T & U;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source1 The first source object from which to copy properties.
* @param source2 The second source object from which to copy properties.
*/
assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source1 The first source object from which to copy properties.
* @param source2 The second source object from which to copy properties.
* @param source3 The third source object from which to copy properties.
*/
assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param sources One or more source objects from which to copy properties
*/
assign(target: any, ...sources: any[]): any; I might have read your question wrong. Just thought I'd try to be helpful still 🌹 |
@basarat that works except I would still have to define each partial interface as in the below example. I suppose it's just a convenience factor, but it would be nice if I didn't have to define PersonName to get typing information on the name in the second arbitrary object. Having the second interface just seems like a nightmare to maintain.
I guess a possible shortcut is
|
@mjohnsonengr I blindly copy pasted from function assign<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = source[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 }); // Error |
@basarat Ohh! Now that's fancy! I'll have to play with that. Thanks for pointing that out to me!! |
One question, given this modified example from (my own example) above:
So, my question is, is an object with an optional field not included on the 'extended object' a valid subtype? |
@olmobrutall only when the object is "fresh". If there's a level of indirection where the type is manifested into a declaration, those errors no longer occur. Refer to this example from my previous longer comment: interface MyState {
width: number;
size: string;
}
function setState(x: partial MyState) { /* ... */ }
let newOpts = { width: 10, length: 3 }; // Incorrect name 'length'
setState(newOpts); // OK but suspicious if not wrong |
Got it! This compiles var temp = { name: "John", age: 13, gender: "Male" };
var john: { name: string; age?: number } = temp; So TS uses the heuristic that if you are using a literal object you don't want inheritance stuff, preventing errors. I think this will work for 99% of the If this is a problem: interface MyState {
width: number;
size: string;
}
function setState(x: partial MyState) { /* ... */ }
let newOpts = { width: 10, length: 3 }; // Incorrect name 'length'
setState(newOpts); // OK but suspicious if not wrong then this is also a problem that we have today: interface Person{
name: string;
age: number;
}
function savePerson(x: Person) { /* ... */ }
var temp = { name: "John", age: 13, gender: "Male" };
savePerson(temp) In practice this error doesn't happen because you want to declare the type as soon as possible to get auto-complete for the members. |
Just to clarify, I'm not suggesting that If in the future |
Now that 2.0 is out, will this be added to the 2.1 roadmap? I don't see it there currently. |
How will partial class A { // adding on class A
}
partial interface A { // not adding on interface A but makes properties optional
} |
FYI: Looks like this has been resolved with #12114. |
Indeed |
How do mapped types fill the need of the various |
@cjbarth See here for some examples. It's pretty much a giant sledgehammer solving several constraint problems at once. // Example from initial report
interface Foo {
simpleMember: number;
optionalMember?: string;
objectMember: X; // Where X is a inline object type, interface, or other object-like type
}
// This:
var foo: Partial<Foo>;
// Is equivalent to:
var foo: {simpleMember?: number, optionalMember?: string, objectMember?: X};
// Partial<T> in that PR is defined as this:
// Make all properties in T optional
interface Partial<T> {
[P in keyof T]?: T[P];
} |
Please note that |
You guys are all wizards to me. Hats off! |
@mhegazy When you say it is in the "default library file", does that mean I should be able to use it with TS 2.1.x? Do I need to import something? It doesn't seem to work. |
@xogeny yes, keep an eye out for it in TypeScript 2.1 (or in our nightly builds). |
@DanielRosenwasser I'm still confused. As far as I can tell, I'm running TypeScript 2.1 and I don't see it. Are you saying this will come out in a patch release of 2.1? |
|
Ah! OK, now I understand. I didn't realize these were release candidates. It would be much clearer if the version number reflected that, but I trust you had a good reason for doing it this way. I look forward to the final version! |
From { name: 'typescript',
description: 'TypeScript is a language for application scale JavaScript development',
'dist-tags':
{ latest: '2.0.10',
next: '2.2.0-dev.20161129',
beta: '2.0.0',
rc: '2.1.1',
insiders: '2.0.6-insiders.20161017' }
} From a release tag perspective on npm, it is labelled a RC and will not install automatically. Also the commit tag in GitHub also refects the nature of the release: https://github.com/Microsoft/TypeScript/releases/tag/v2.1-rc. I believe the challenge is that some of the wider tooling requires installable versions to have a proper npm semver number (e.g. #.#.#). |
Another good approach is to auto generate files without break the user code, then we can auto generate code using transformation templates. |
Potential Use Cases
deepPartial
)React.setState
(partial
)The text was updated successfully, but these errors were encountered: