Typescript: Support proposed ES Rest/Spread properties

Created on 21 Feb 2015  ·  96Comments  ·  Source: microsoft/TypeScript

es7 proposal : https://github.com/sebmarkbage/ecmascript-rest-spread

Spread properties

Typing

In my opinion the goal of this method is to be able to duplicate an object and changing some props, so I think it's particularly important in this case to not check duplicate property declaration :

var obj = { x: 1, y: 2};
var obj1 = {...obj, z: 3, y: 4}; // not an error

I have a very naive type check algorithm for a similar feature (JSXSpreadAttribute) in my little jsx-typescript fork: I just copy the properties of the _spread object_ in the properties table when I encounter a spread object, and override those property if I encounter a declaration with a similar name.

Emitting

jstransform use Object.assign, babel introduce a shim:

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

We could either force the presence of assign function on ObjectConstructor interface, or provide a similar function (with a different name).

I think that the optimal solution would be to not emit any helper in es6 target (or if Object.assign is defined), and to emit an helper function for es5, es3.

var obj = { x: 1, y: 2};
var obj1 = {...obj, z: 3};

/// ES6 emit
var obj = {x: 1, y: 2};
var obj1= Object.assign({}, obj, { z: 3 });

//ES3 emit
var __assign = function (target) { 
    for (var i = 1; i < arguments.length; i++) { 
        var source = arguments[i]; 
        for (var key in source) { 
            if (Object.prototype.hasOwnProperty.call(source, key)) { 
                target[key] = source[key];
            } 
        } 
    } 
    return target; 
};

var obj = {x: 1, y: 2};
var obj1= __assign({}, obj, { z: 3 });

Rest properties

Typing

For simple object the new type is a subtype of the assignation that does not contains properties that has been captured before the rest properties :

var obj = {x:1, y: 1, z: 1};
var {z, ...obj1} = obj;
obj1// {x: number; y:number};

If the destructuring assignment has an index declaration, the result has also a similar index declaration:

var obj: { [string: string]: string };
var {[excludedId], ...obj1} = obj;
obj1// { [string: string]: string };

new/call declarations are obviously not captured:

var obj: { (): void; property: string};
var { ...obj1} = obj;
obj1// { property: string };

Emitting

It is not possible to emit _rest properties_ without an helper function, this one is from babel:

var obj = {x:1, y: 1, z: 1};
var {z, ...obj1} = obj;
var __objectWithoutProperties = function(obj, keys) {
    var target = {};
    for (var i in obj) {
        if (keys.indexOf(i) >= 0) continue;
        if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
        target[i] = obj[i];
    }
    return target;
};

var obj = {x:1, y: 1, z: 1};
var z = obj.z;
var obj1 = __objectWithoutProperties(obj, ["z"]);

_Edit: added some little typing/emitting example_

Committed ES Next Fixed Suggestion

Most helpful comment

I'm not sure how we'll eventually ship it, but I started work on rest/spread in object literals and destructuring. Right now the plan is to use our __assign polyfill for emit.

All 96 comments

We'd probably need some examples of how this could plausibly be emitted in downlevel (if that's in scope).

@RyanCavanaugh, I added some little emitting example and typing idea.
I would like to work on that one since anyway it's more or less a jsx feature and I will need to add it to my fork, but if it's added to typescript core it's better ^^.

I recently discovered this feature in ES7 via this article. I have to say, it's quite handy!

Would love to see this in TypeScript!

Any updates on this? Would love to see this in 1.6 as it would come super handy when using with React :smile:

@prabirshrestha for the record, we currently have the property spread operator for JSX on master.

@DanielRosenwasser Could you elaborate? Does that mean this works on master with the --jsx flag? Does that include rest properties or just spread properties?

Sorry @mnpenner and @prabirshrestha, yes, I meant on master. @RyanCavanaugh knows more about this than I do, but I believe it's just spread properties.

JSX supports spread _attributes_, e.g. <TagName {...spreadedExpr} />. Nothing to do with the ES6 spread operator other than sharing the same token and having roughly equivalent semantics.

I'm very interested in both Redux and React, the manipulation in state is made much easier with this. Would love to see this implemented.

We want to wait for the proposal to reach Stage 3 before addressing this.

It's very useful in react and redux , please implement it as soon as possible , please :-)

Here's a helper module I've been using to work around this. It's not ideal as you have to specify all the keys twice, once on the left hand side and once as strings on the right hand side, but I can't think of a better way to do it. Any feedback welcome, I'm sure there's edge behaviour I've missed!

https://gist.github.com/tomduncalf/fbae862b123445c117cb

Is this something you'd accept a patch for? (to be merged once it reaches stage 3). And if so, have any pointers of where to start making changes to support it?

This is the last ES7+ feature that my team heavily uses that's preventing us from switching over to typescript; would definitely love to have to sooner, if possible.

We would consider a PR here. One note though, wiring the type system for this feature is not a simple task. If you are interested in perusing this I would keep it incremental, and would start by parsing then emit then type checking. @sandersn should be able to help you if you have any questions.

Just a note for those interested in using this with Redux. I agree the notation is convenient. But I've actually been getting by just fine without it by leverage the updeep library. I created some basic typings for it and it works pretty well. I even have a simple library that implements an updeep based store and actions. If anybody is interested in this, just contact me.

In any case, :+1: for spread operators in TS. :smile:

@xogeny I would be very interested in having a look at that library. Thanks

@cur3n4 I have a repository where I've been playing with these ideas. It changes quite a bit (since this is all still evolving). I was using a special modules that leveraged updeep initially and that functionality is still in the library. But I've stopped using it because I found it difficult to maintain the necessary type constraints. I suspect if Typescript had something along the lines of Java's super generic type constraints, updeep (and several other libraries, like lodash or ramda) could be made a lot more type safe.

But don't get me wrong, I still think a spread operator would be really useful. It would actually allow you to express and do things safely and tersely in the language that I don't see how to do with the current type system (_i.e.,_ allowing you to describe the right type constraints on external libraries). For the moment, I sacrifice the terseness for the safety (while the updeep approach sacrificed safety for terseness).

After using Babel for past few months, converted my project to TS and had to go and redo all these. Would love to see this implemented!

:+1:

would love to see this.

+1

must have feature, please implement

+1

+1 much nicer syntax than Object.assign + JSX support

+1 it a must have for react development for passing props down (var { checked, ...other } = this.props;. see the react docs

it a must have for react development

Let's be honest, it is a convenience, not a must have. The proposal is at Stage 2 with TC39. The TypeScript team have made it clear, they don't want to consider implementing things that aren't originated in TypeScript until they are Stage 3. The place to "+1" is with TC39.

@kitsonk - you can't expect good adoption when frameworks like react / etc specifically call out these types of things in their docs. People will start using Typescript, see all these issues and go back to Babel. Thats what i did.

Well, I think we are talking about a broader topic. Not one browser vendor will implement anything that isn't in Stage 3 (unless they personally like it). TC39 have been trying to give some level of organisation for their part of the web. TypeScript has gotten bitten, hugely, by jumping the gun before (modules, and look at the chaos that that has caused since). I respect that they are trying to at least put some guidance and structure around what they are willing to implement and when. Is there some other suggestion of what standard should be used? I don't think "because X framework mentions it in their docs" would be a good one.

frameworks like react / etc specifically call out these types of things in their docs

Which they mark as only a proposal and then go on to instruct you how to jump through hoops to configure Babel 6 to use it. I would rather blame frameworks for depending on syntactical technologies that are only at a draft specification. Personally, we in Dojo, got burned by Object.observe and I am reticent again to try to depend on technologies before they get to Stage 3 in Dojo.

The particular path is a lot more than just supporting transforming the syntax on emit for TypeScript. There are all the type inference that needs to follow with it. I have looked at the draft spec to understand, but where do symbols go? Where do non-enumerable properties go? I assume in both cases they just disappear into the ether, but there will be all sorts of type splitting and merging that will need to go on in the background for this, so I can totally understand that TypeScript team saying "ok, let's hold off on this until TC39 has really kicked the tires on this".

Overall I think that TS is quite well featured today. There was a time (around 1.4 ~ 1.5 I think) where I was frustrated by lack of some ES2015 features, but as of today TS has catched up pretty well with the standard.

Of course, there is sometimes feature envy wrt other languages. For instance, Babel offers pretty much any JS proposal, including "experimental" stuff. Personally, I am looking forward to this issue and the function bind operator as well.

But at the same time we have to acknowledge that the TS team takes compatibility a lot more seriously than Babel does. They don't want to change the semantics of your code between releases, or simply remove a feature, which is what Babel does. For some (enterprise?) projects that's important.

I can also understand that MS doesn't want to invest a lot of resources into features that may get dropped, the Object.observe fiasco comes to mind. Actually, they said they would consider a PR on this one for instance :wink:.

In the past some features were added early behind an experimental compiler switch (e.g. async), which meant you had to opt in and acknowledge that the feature might change or get removed in future releases. Maybe that could be done again for the most popular requests?

Just a quick note here. We have been rather slow in adopting stage 0-1 proposed ES features mainly due to backward compatibility concerns. We do follow the discussions in the TC39 meetings closely, and participate directly in the meetings or in proposal preparations before the meetings; and when we put a feature in we would like to know what that means to users who take a dependency on them.
If you look at the TC39 process documentation a feature at stage 1 can expect "Major" post acceptance changes. This was the cases for classes, modules, iterators, and almost all non-trivial ES6 features..
Putting my user hat on, breaking changes to syntax can be mechanical to respond to, whereas breaking changes to semantics can be extremely hard to catch and fix; this can be a huge cost on teams using any of these features, and/or a blocker for adopting newer versions of the tools. and we would like to minimize that when possible. The is why we have adopted a policy to enable features by default when they reach stage 3, and under an experimental flag before that (e.g. decorators).
Having said that, the object property spread and rest are on our roadmap, and we do plan to tackle them in a future release.

Maybe it is worth consider to add to compiler ability to inject some 3rd party extensions, as done in babel ?

Another thought: what about support for preprocessing typescript sources (with Babel)?

If we can get Babel (or whatever library) to expand object spreads into Object.assign calls, that might work well enough? It'd hopefully be generalizable to other experimental features implemented in Babel, too.

@kitsonk - broader topic absolutely, dang wish github had better discussions ;). I disagree w/ you quite a bit tho:

. TypeScript has gotten bitten, hugely, by jumping the gun before (modules, and look at the chaos that that has caused since).

They went off and did their own implementation mirroring .NET not using anything from the community, thats their own fault.

put some guidance and structure around what they are willing to implement and when

they implement decorators ( stage 1 ), class properties ( stage 1 ), etc.

I don't think "because X framework mentions it in their docs" would be a good one.

your kidding right? react is the most popular framework and has a projection to stay that way for awhile now. not supporting that will/has really hurt adoption.

I think TypeScript has a lot of catching up to do with Babel and its got a lot of challenges such as: really difficult to start using it with an existing app, migrating from babel to typescript for A2 is a NIGHTMARE!, lack of decoupled plugins makes working w/ Node extremely difficult.

your kidding right? react is the most popular framework and has a projection to stay that way for awhile now. not supporting that will/has really hurt adoption.

I have a lot of respect for React, but I am not kidding. It isn't the only game in town and it is certainly on a large hype curve. 6 months ago it was React + Flux, now React + Redux is the flavour of the day. A year ago, Angular and Polymer. Two years ago it was Backbone and Ember. Six years ago it was Dojo, MooTools, jQuery, gwt, ExtJS, etc...

don't start framework wars here :exclamation:
{..., } syntax is much handsome as is, no matter any framewrok

They went off and did their own implementation mirroring .NET not using anything from the community, thats their own fault.

@amcdnl What exactly are you referring to? I can only assume you are referring to the module as namespace issue, which has nothing to do with .NET and is also not one of the major pain points with modules in TypeScript.

+1 for this to be included in typescript node js is supporting it exprimentally

let typescript = { ...typescript, object_spread }; // Yes please.

Bummer there's no movement on this, I really hope this reaches stage 3 soon. Going to be killing syntax error highlighting in VSCode if possible

I wonder if Salsa changes anything to the game?

I understand that TS doesn't want to implement unstable features early: limited resources, shielding us users from possible breaking changes, etc.

But now that Salsa is VS Code default JS engine, the pressure for newer JS syntax (at least just the syntax, not full TS typing) is going to increase. Especially given the popularity of Babel.

Is Salsa going to be able to accept a broader syntax faster than TS?

+1

Is this going to be in 2.1 rather than 2.0? :cry:
https://github.com/Microsoft/TypeScript/wiki/Roadmap#21

Considering how large 2.0 is already turning out to be, I'm not too suprised.

We try to keep the releases 6-8 weeks apart; so that limits what can go in. we are doing some emitter refactoring at the moment, and should be able to add this feature once that is completed.

So is this fixed?

...standalone helper function for spread attributes
https://github.com/Microsoft/TypeScript/releases/tag/v1.8.10

Since I am still getting "Property restructuring pattern expected"

Not exactly. That fix is specifically for JSX spread attributes:

const props = { foo: "bar" };
return <SomeComponent {...props} />;

which already worked, before React v15 removed an undocumented internal function that TypeScript's JSX compiler depended on. Oops 😃

Object spread is a different proposal, with an admittedly similar syntax (it may have even been proposed by Facebook engineers and inspired by the JSX equivalent, although I don't know for sure).

So since {...props} does not yet work, how would you using TypeScript achieve something similar to that?

So since {...props} does not yet work, how would you using TypeScript achieve something similar to that

Use xtend : https://www.npmjs.com/package/xtend it has great typings : https://github.com/typed-typings/npm-xtend (by @blakeembrey :rose:) thanks to the intersection type

Or don't use any library at all!

const newProps = Object.assign({} /*new object*/, props /* add all attributes of props */, {
   // add additional props
  bar: "baz"
});

It's a bit verbose, but it _is_ native to ES2015, so if you've already got a polyfill for that, you're solid.

This operator is one of the main reasons we decided to use babel in a project instead of typescript.

Completely changes the game for handling objects immutably.

As alternative is there anything planned that let you plug in custom transformation to TSC like you can do in babel? Maybe even based on the same API, that would be awesome and solve problems like this.

As alternative is there anything planned that let you plug in custom transformation to TSC like you can do in babel

@Niondir Yes. Just search for the [Transforms] tags. These are needed to get proper async/await/generator transpilation into the TypeScript compiler :rose:

After a little search, you are referencing this? https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API

Sounds great.I just do not finde anything related to the Object Spread for that API. Maybe it would be a nice little project to solve this issue based on the Compiler API :)

@Niondir sorry for not providing better help in my original message. I am talking about https://github.com/Microsoft/TypeScript/issues/5595 < which will allow pluggable emit for ESNext syntax. TypeScript is a bit more involved than Babel because it needs to have _semantic_ type system understanding of your code so object spread work will probably need to come from TypeScript team.

Having a plugin based emitter would make this easier. Yes you would use the compiler API (or potentially fork the compiler in the beginning till a plugin system is made public + for the semantic / scanner logic)

This might be a question for another issue, but wouldn't it be possible to add an option "allow-experimental" and have TypeScript allow future things? For example, allow spread operator and output as is. I can handle that with babel afterwards.

This is a must have feature for some projects I'm working on and I cannot migrate without essential stuff like this. It would be awesome if I could.

I'm not sure how we'll eventually ship it, but I started work on rest/spread in object literals and destructuring. Right now the plan is to use our __assign polyfill for emit.

This is the best news that i heard in past 3 months!

Unfortunately, it turns out that getting this to work with generics is a _lot_ of work. You have to properly support code like this:

function addId<T>(t: T): {...T, id: number} {
    return { ...t, id: 1 };
}

As you can see, addId returns a new kind of type, an object type that includes a spread operator.

After some discussion, we decided to delay until 2.1 and look at this again. We need to focus on getting 2.0 features done right now.

Forgive my ignorance... but it feels like addId in that case be able to return T & { id: number }? Or is there some quirk of union types that stops that from being an optimal solution?

I think he mean that actually T should be checked, since you can pass anything to T

addId<boolean>(true);
addId<number>(5);

Babel silently ignores such statements let a = { ...true } but i think they shouldn't be valid

It's not T & { id: number } because if T has an id property, it will get overridden.

On the other hand, the intersection, the resulting type of id would be the intersection of T's id type and number.

So what @sandersn is saying is that you need a separate type operator.

While we'd definitely benefit from a better typing solution here, Object.assign already exists and naively use intersections as an output type. Why couldn't we have, as a stop gap measure, the rest operator doing exactly what Object.assign does ?

It would ease a lot of concerns about the missing feature and it wouldn't bring unwanted behaviour since people already use Object.assign instead.

Was actually extremely surprised this wasn't supported.

As soon as we get this the merrier. Will work great with react!

I would be happy even if it was not typed

@2426021684 It might sound reasonable but consider the implications. If it is not typed, that is to say typed as any, any uses of the syntax will percolate any to the leaves of expressions.

@aluanhaddad it would be the same as Object.assign which is the workaround everyone has to use because they have no choice (see JabX post above)

Please consider implementing @JabX's suggestion in the near term. This proposal is stage 3 in all but name, it is _definitely_ going to be a part of the standard, and has very clear and simple semantics. Putting in place the syntactic support for property spreads + naive typing of Object.assign would be a very helpful stopgap while we wait for the "right" implementation. Don't let perfect be the enemy of good.

Rest properties is extremely important with React 15.2 as can be seen here https://facebook.github.io/react/warnings/unknown-prop.html

const divProps = Object.assign({}, props)
delete divProps.layout

is very ugly, especially if the amount of props on a component is higher

for those who can't wait anymore here is a workaround:

function steal(result: any, data: any): any {
    for (var key in data) {
        if (value.hasOwnProperty(key)) {
            result[key] = data[key];
        }
    }
    return result;
}

export class SameAs<a> {
    constructor(public result: a) { }
    public and<b>(value: b): SameAs<a & b> {
        return new SameAs<a & b>(steal(this.result, value));
    }
}
export function sameAs<a>(value: a): SameAs<a> {
    return new SameAs(steal({}, value));
}

// example of use:

const mixture = sameAs(one).and(anotherOne).and(yetAnotherOne).result; // <-- ta-da!

Ignore this post, see below for a better implementation

Here is what I came up with for a poor (typescript) coder's destructuring operation:

declare interface ObjectConstructor {
  destruct<T extends Object>(data: T, props: any): T;
  destruct<T extends Object>(data: T, ...propNames: string[]): T;
}

function destruct<T extends Object>(data: T, ...removals: string[]) {
  const rest = <T>{};

  const keys = removals.length === 1 && typeof removals[0] === 'object' ?
    Object.getOwnPropertyNames(removals[0]) :
    <string[]>removals;

  Object
    .getOwnPropertyNames(data)
    .filter(x => keys.indexOf(x) < 0)
    .forEach(x => {
      (<any>rest)[x] = (<any>data)[x];
    });

  return rest;
}

Object.destruct = destruct;

// Usage example:

const srcObj = { A: 'a', B: 'b', C: 'c' };
// destruct using an object template
const onlyC = Object.destruct(srcObj, { A: null, B: null });
// destruct using property names
const onlyA = Object.destruct(srcObj, 'B', 'C');

The obvious advantage of using an object layout is that you can type the layout and at the very least get type conflicts if you happen to refactor anything.

Update (also ignore this, see below for even better)

I have reworked this (after messing around with it in a real world environment) and came up with some much easier to work with functions.

function deconstruct<TResult, TData>(
  result: TResult,
  data: TData,
  onHit: (result: TResult, data: TData, x: string) => void,
  onMiss: (result: TResult, data: TData, x: string) => void,
  propNames: string[]
  ) {

  Object
    .getOwnPropertyNames(data)
    .forEach(x => {
      if (propNames.indexOf(x) < 0) {
        if (onMiss != null) {
          onMiss(result, data, x);
        }
      }
      else {
        if (onHit != null) {
          onHit(result, data, x);
        }
      }
    });

  return result;
}

// shallow clone data and create a destructuring array of objects
// i.e., const [ myProps, rest] = destruct(obj, 'propA', 'propB');
function destruct<T>(data: T, ...propNames: string[]) {
  return deconstruct(
    [ <T>{}, <T>{} ],
    data,
    (r, d, x) => (<any>r[0])[x] = (<any>d)[x],
    (r, d, x) => (<any>r[1])[x] = (<any>d)[x],
    propNames
  );
}

// shallow clone data and create a destructuring array of properties
// i.e., const [ propA, propB, rest] = destructProps(obj, 'propA', 'propB');
function destructProps(data: any, ...propNames: string[]) {
  return deconstruct(
    [ <any>{} ],
    data,
    (r, d, x) => r.splice(r.length - 1, 0, d[x]),
    (r, d, x) => r[r.length - 1][x] = d[x],
    propNames
  );
}

// shallow clone data and remove provided props
// i.e., const excluded = omit(obj, 'excludeA', 'excludeB');
function omit<T>(data: T, ...propNames: string[]) {
  return deconstruct(
    <T>{},
    data,
    null,
    (r, d, x) => (<any>r)[x] = (<any>d)[x],
    propNames
  );
}

The typing on these functions makes things much easier to work with. I am specifically working with these in React, so having strong typing on my known props is very handy. There is an additional piece of code using these functions that is particularly handy for react:

const [ props, restProps ] = destruct(omit(this.props, 'key', 'ref'), 'id', 'text');

In this case, props is typed just like this.props, but doesn't contain any of the props meant for transferring down the line (which live now in restProps)

IMO, not much is gained by posting non-typesafe workarounds for this problem.

agreed, i'm working on a more typesafe version right now.

Another stab at this:

The main object extension:

declare interface ObjectConstructor {
    rest<TData, TProps>(data: TData, propsCreator: (x: TData) => TProps, ...omits: string[]): { rest: TData, props: TProps };
}

function rest<TData, TProps>(data: TData, propsCreator: (x: TData) => TProps, ...omits: string[]) {
  const rest = <TData>{};
  const props = <TProps>propsCreator.apply(this, [ data ]);

  Object
    .getOwnPropertyNames(data)
    .filter(x => props.hasOwnProperty(x) === false && omits.indexOf(x) < 0)
    .forEach(x => {
      (<any>rest)[x] = (<any>data)[x];
    });

  return {
    rest,
    props,
  };
}

Object.rest = rest;

A React specific extension:

declare module React {
  interface Component<P, S> {
    restProps<T>(propsCreator: (x: P) => T, ...omits: string[]): { rest: P, props: T };
  }
}

function restProps<P, S, T>(propsCreator: (x: P) => T, ...omits: string[]) {
  return Object.rest((<React.Component<P, S>>this).props, propsCreator, ...omits.concat('key', 'ref'));
}

React.Component.prototype.restProps = restProps;

Some sample usage:

const src = { A: 'a', B: 'b', C: 'c' };
const { rest, props } = Object.rest(src, x => {
    const props = { A, C } = x;
  return { A, C };
});

And a sample of using the react extension:

const { rest, props } = this.restProps(x => {
  const { header, footer } = x;
  return { header, footer };
});

All typing should be preserved. the only part that bugs me is the props creator because it requires duplication. I think I could hack it and assume the destructured object lives at _a but that seems _dangerous_...
_NOTE_: _a is not even a viable hack, as it would be equivalent x.

Here is a Fiddle to play around with.

I'm thinking returning an array might make more sense, as you can rename the outputs as needed.

scratch that, this would prevent getting proper typing for the props.

Since mostly people here want to use this with react, why not implement this just for *.tsx files until it reaches stage 3?

Redux reducers can be written as *.tsx files too, just obj typecasting instances should be converted to obj as Type

This very useful not only for react, but in redux reducers for example.

I think the tags and missing milestone on this are a bit out of date. Because this is committed and being worked on (#10727) for TypeScript 2.1. So no need to debate the value of it anymore.

ping @mhegazy

Updated the tags. I should clarify that the reason this has not been implemented yet is not that the value, but rather the complexity of the type system implementation. the transformation it self is rather trivial, i.e. {...x} into Object.assign({}, x). the issue is how these types are pretested and how they behave. for instance for a generic type parameter T is {...T} assignable to T, what about {x: number, ...T}, what about if T has methods, etc.. https://github.com/Microsoft/TypeScript/issues/10727 provides a more in depth explanation of the type system changes needed.

I fully appreciate that the type system augmentations required for an ideal treatment of this feature are not trivial, and it's great to see that they are being enthusiastically tackled! I would just reiterate that,

putting in place the syntactic support for property spreads + naive typing of Object.assign would be a very helpful stopgap while we wait for the "right" implementation. Don't let perfect be the enemy of good.

It seems like the proposal has reached stage 3: https://github.com/tc39/proposals

@ddaghan your example with tsx won't work

any timeframe for this feature?

@SpyMaster356 I've been lurking this for a while and it looks like it's close. @sandersn has been kicking ass on this for (at least) the past few weeks. 🙌

You can follow along here (https://github.com/Microsoft/TypeScript/pull/12028) and here (https://github.com/Microsoft/TypeScript/pull/11150)

Some one should update the Roadmap

It seems that using this feature allows the assignment of unknown props:

interface State {
  a: string;
  b: number;
}

let state: State = { a: "a", b: 0 };

state = {...state, x: "x"}; // No error

I was expecting an error that x is not a known property of State. Is this not how the feature works?

For example, my current workaround before this PR was this:

state = update(state, { x: "x" }); // Error: Property 'x' is missing in type 'State'.

function update<S extends C, C>(state: S, changes: C): S {
  return Object.assign({}, state, changes);
}

Is it possible to achieve this with object spread/rest?

Object Rest and Spread, as per the ES proposal, behaves similar to Object.assign. the last mention of the property "wins". no errors are reported. in the example you had, the type of {...state, x:"X"} is { a: string, b:number, x:string }; and this type is assignable to State and thus the assignment works. this is the same as saying let state: State = { a: "a", b:0, x: "X" }; would be allowed as well.

But that's what I'm confused about: let state: State = { a: "a", b:0, x: "X" } gives the error Object literal may only specify known properties, and 'x' does not exist in type 'State' which is what I want... why is it a valid assignment when coming out of an object literal containing a spread?

sorry.. object literals are a special case. my example was wrong. here is a better example:

let obj = { a: "a", b:0, x: "X" };
let state: State = obj; // OK

The issue here is if rather subjective. When the compiler sees let state:State = { a: "a", b:0, x: "X" }, chances are x is a typo, either a stale propoerty that was left off after refactoring, or a type for an optional property, that is why it is reported as an error.

however, if you spread an object, let's say let myConfig : State= { a: 1, ...myOtherBigConfigBag}, if the myOtherBigConfigBag has a few properties that you do not care about, you just need the a and the b, an error here would force you to keep these two interfaces in sync, or cast to make these errors go away.

that said. we should reconsider this decision. filed https://github.com/Microsoft/TypeScript/issues/12717 to track that.

I see. I love your idea in #12717, that's exactly what I was thinking. I would actually like such behavior even for the spread props but I can see your point that it's very subjective and might be annoying for some common JS use cases.

@aaronbeall I agree, it would be annoying for common JS use cases... Most of the time, you just want to ensure the object has the shape of the interface specified and not directly inspect the spread object, the current implementation is okay IMO

Hi guys, Congrats for the great release! Now is time for a deserved rest... speacking of whitch I've an issue with the rest operator :)

Using React you tipically have a component like:

export interface MyLinkProps extends React.HTMLAttributes {
    myUrl: string
}

class MyLink{

    render(){
      const {myUrl, ...attrs } = this.props;
     return <a href={calculate(myUrl)} ...attrs>Click here</a>;
   }
}

The issue is that when you hover with the mouse over attrs you get the list of all the properties (hundreds) instead of React.HtmlAttributes.

I know that typescript is structurally typed and all that, but could be possible to assign an explicit type to the rest variable?

Some alternatives:

    const {myUrl, ...attrs as React.HtmlAttributes } = this.props; //Is not really a casting

    const {myUrl, ...attrs : React.HtmlAttributes } = this.props; //conflicts with ES6?

    const attrs : React.HTMLAttributes; 
    const { myUrl, ...attrs } = this.props;  //re-declare the variable

@bondz Well, its not true in 100% of the uses in my project. :) But in another project it may be very annoying. In my case I'm using Redux and React and making heavy use of immutable state, which means to update or create state objects I must copy all props onto a new object literal... in all cases I want 100% type safety that I'm trying to copy the right data onto the target state interface. Currently I use functions to ensure this safety, but I would prefer to use object spread because its cleaner (readable, expressive, standard syntax.) But in someone else's project they might want different behavior because they are using a lot of untyped or loose object structures, so I see how it's quite subjective. I think the #12717 suggestion is a good middle ground (and more consistent with existing object literal type safety.)

https://github.com/Microsoft/TypeScript/issues/2103#issuecomment-145688774
We want to wait for the proposal to reach Stage 3 before addressing this.

Seems it's already state 3, any plan of supporting this?

Btw, we use VSCode mainly for ES development, not TS yet :)

@evisong this feature has shipped and is already available in the latest version of vsCode. Update your vsCode to version 1.8

Was this page helpful?
0 / 5 - 0 ratings