Repository navigation
Easier destructuring with type annotations on binding patterns #29526
Description
Search Terms
type inference destructuring syntax conflict
Suggestion
It's currently not possible to destructure a value in Typescript and provide types for each of the values due to the syntax clash with destructuring and renaming at the same time. You can see exactly this issue in the Typescript FAQs at: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-use-x-in-the-destructuring-function-f-x-number------
This is frustrating when programming in React where it's very common to see this pattern:
const MyComponent = ({ a, b }) => {
// ...
}But in Typescript a and b are untyped (inferred to have type any) and type annotation must be added (either to aid in type safety or to avoid compiler errors, depending on the state of the user's strict flags). To add the correct type annotation it feels natural to write:
const MyComponent = ({ a : string, b : number }) => {
// ...
}but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:
const MyComponent = ({ a, b } : { a : string, b : number }) => {
// ...
}Which is very strange to write and difficult to read when the object has more than two parameters or the parameters have longer names. Also the value names have been duplicated -- once in the destructuring and once in the type annotation.
I suggest we allow some other symbol (my current thinking is a double colon) to make the syntax unambiguous in this specific scenario:
const MyComponent = ({ a :: string, b :: number }) => {
// ...
}Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:
const a :: string = "";
const b :: number = 1;Use Cases
It would allow for type-safe destructuring of values where the type cannot be inferred by the compiler (such as function parameters).
Examples
A good example of the sort of React components I'm talking about (and one of the first Google results for React Functional Components) can be found at https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc. We can use my proposed syntax in the functional component definition:
import React from 'react'
const HelloWorld = ({name :: string}) => {
const sayHi = (event) => {
alert(`Hi ${name}`)
}
return (
<div>
<a href="#"
onclick={sayHi}>Say Hi</a>
</div>
)
}Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Activity
j-oliveras commentedon Jan 22, 2019
Duplicate/related to #7576, #29019
dragomirtitian commentedon Jan 22, 2019
I know expression level syntax is a no-no, but how about:
With the
...meaning auto-spread all the rest of the properties in the object.You can still have a traditional binding list if you want to remap any names:
There is a reason this issue keeps poping up. The current solution is painful.
RyanCavanaugh commentedon Jan 23, 2019
I agree it's a duplicate but it really does suck. We should try again.
[-]A new sigil for specifying type when deconstructing[/-][+]Destructuring with type annotations (take 2)[/+][-]Destructuring with type annotations (take 2)[/-][+]Easier destructuring with type annotations on binding patterns[/+]olmobrutall commentedon Feb 6, 2019
With React hooks just released, class components are being... slowly deprecated in favor of functional components all the way.
This feature is now more important than ever.
The :: syntax sounds good to me.
scottmas commentedon Apr 30, 2019
There are indeed multiple duplicates of this request. Heavyweights of the JS ecosystem have said they want it. Random dudes such as myself want it. Unscientific polls show that a large majority of JS developers use argument object destructuring (https://twitter.com/rauschma/status/987471929585143809). It seems to me that the time has come for some solution to be made :)
FWIW, the double colon syntax seems good to me.
TazmanianD commentedon May 23, 2019
Yeah, this seems like the 3rd iteration of this issue with the previous two just being closed as being too complicated to implement but I suspect that people are going to continue to ask for it and I'll add my voice to the list. This is something that's actually making it harder for me to convince my teammates to switch to TypeScript because this type of destructuring in function calls is pretty common in our existing code.
gynet commentedon Jul 31, 2019
The current syntax for this scenario does look bad... I wish ES6 could choose different syntax for renaming, instead of the colon, e.g 'as'; the 'as' keyword is more meaningful for renaming purpose :)
Anyway, although the proposal of double colon syntax does not look bad, it could ergonomically cause troubles for developers to understand what does it mean since people get used to using a single colon as the type annotation. I would prefer another way to address the problem. Actually, I like dragomirtitian's proposal better.
Richiban commentedon Jul 31, 2019
While I also think Titian Cernicova-Dragomir (@dragomirtitian) 's solution is a reasonable one, I'd like something more in keeping with Typescript's philosophy of not introducing any new syntax other than type annotations. one of the reasons for Typescript's success has been its mantra of "It's just Javascript, but with types". It's being able to look at a declaration and immediately parse out what's TS and what's JS:
If TS starts introducing its own expression / destructuring syntax I fear that starts us down a slope of TS being its own language, rather than a type system for JS.
dragomirtitian commentedon Jul 31, 2019
Actually the more I think about
::the more I like it 😁, but with a slightly different meaning. Let's not consider::as a new way to introduce a type annotation, but rather an empty rename.I personally think it flows nicely. The first
:always introduces a name binding, which can be empty, the second:always introduces a type annotation, and since a new:not valid here in ES it would not be ambiguous.Would work decently with objects too:
Richiban commentedon Jul 31, 2019
Titian Cernicova-Dragomir (@dragomirtitian)
Works for me! 👍
gynet commentedon Aug 2, 2019
Titian Cernicova-Dragomir (@dragomirtitian) Good point!
131 remaining items
mick62 commentedon May 25, 2024
There is no change needed on JS side. The transpiler would simply throw away the additional parameter names:
fun(foo: 'a', bar: 'b', 'c') ---> fun('a', 'b', 'c')
FeldrinH commentedon May 25, 2024
It's worth noting that type hints when destructuring are useful for more than named function parameters. For example Svelte 5 uses them for declaring types of component props (see https://svelte-5-preview.vercel.app/docs/runes#$props).
FeldrinH commentedon May 25, 2024
But what if you have function calls with different argument orders? For example, what if you have
fun(foo: 'a', bar: 'b', 'c')in one place andfun(bar: 'b', foo: 'a', 'c')in another?mick62 commentedon May 25, 2024
The call
fun(bar: 'b', foo: 'a', 'c')would be rejected by tsc because the sequence of the actual arguments must comply with the sequence of the formal parameters (as usual).PartMan7 commentedon May 25, 2024
The issue is, named parameters are more than just for reference - one of the very important things they do is allow omitting parameters and being immune to order (such as configs, where you can have dozens of props where only 1-2 might be specified, or React components, where refectoring a component to add a new property lets you change the interface without having to rewrite every single instance).
mick62 commentedon May 25, 2024
Yes, these two common use cases justify to objectify (at least the optional) parameters.
But there is no need to rename properties of the anonymous object type.
So neither
x: y: stringnorx: y:: stringis needed.If we restrict the object type to optional parameters we can use a special syntax to define the object type in a concise way:
fun(foo: string, ?{bar: string, baz: number})
VanCoding commentedon May 25, 2024
mick62 I also like named Parameters, especially how they are implemented for example in kotlin, where the order does not matter if you provide a name. But I don't think they are a replacement to object parameters as we're currently used to in JavaScript/TypeScript. Objects allow so much more things than is possible with named parameters, for example very complex mixins. If named parameters really should be the solution to the topic of the issue discussed here, then they would have to be very powerful, something that would have to be added to JavaScript first, and not something that can just be done in TypeScript.
And that needs a lot of time, if it's even possible to come up with something that's actually good. Additionally, all frameworks would have to make use of this feature first, like react, for example. Props objects would need to be migrated to this new named parameter feature, which is a bit unlikely.
And as long as we have very widely used stuff that requires object parameters like react, it's really desirable to have a TypeScript feature, like the one discussed here. Named parameters are no replacement to it. They would at best be complementary. And therefore, I'm nut sure it makes sense to discuss this here.
But nonetheless, I really like your thinking about this!
otomad commentedon May 26, 2024
It's puzzle to use functions like
forwardRefin React.Although in React we can use
<MyInput />, if we directly call a function like this, how to use it in your opinion?or
? Obviously both of the above are wrong.
otomad commentedon May 26, 2024
In Vue, it uses prop name
classinstead ofclassName. So if you declare a function according to your planclassis a keyword, it can't be used as a variable inside the function anymore unless you rename it.Although you can directly use prop name with
classNameinstead ofclass, users will feel it weird when they use HTML prop withclassand your component prop withclassName.mick62 commentedon May 26, 2024
In the usual way (assuming 'baz' is optional):
I'm not in general against using an object to aggregate parameters where it makes sense like in your example.
But in my (back-end) coding work flow I often need to refactor functions by aggregating the separate parameters into an object just to have "named" parameters (for documentation and to prevent mix-up of arguments).
All the call sites then also need to be refactored. For this use case I want a simpler solution.
As VanCoding pointed out, it's complementary.
PartMan7 commentedon May 26, 2024
I agree with this, but I will say that it is incredibly frustrating to use current types in cases where object destructuring is the way to go (which includes every single React component). Would be greatly appreciated if we could finally get a solution for this, and keep discussion open for ways ahead in the future about other ideas separately.
ehaynes99 commentedon May 27, 2024
Guys, you're discussing a completely different feature now that has nothing to do with this issue. Open another one if you want a new syntax for named parameters. But frankly I don't even see the point of this proposal. It's just ordinal parameters with labels that are then not even associated with variable names on the calling side. The calling side of destructured parameters is already perfect as-is IMO, but even if you disagree, the call signature was NEVER part of this issue.
Beyond that, this is not consistent with the design goal of adding a type system to JavaScript. It's completely unprecedented for TS to add syntax to identifiers, and violates design goal 8:
You state:
It's not a work around, it's a language feature added to ECMA a decade ago. This issue is to improve TypeScript's type type signature for it. This is not the place to debate that feature. Those discussions were concluded a decade ago.
mick62 commentedon May 28, 2024
IMO you overinterprete this design goal which would also disallow the expression
x as string.The expression for the argument is unchanged, only some context is added
fun(foo: x).My first post was a bit provocative :-) . Due to good arguments in follow-up answers I agree that we still need syntactic sugar to declare and use anonymous object types in function definitions.
Allthough someone might argue that a deconstructor expression in a function definition should be protected by goal 8 😉 .
So to get my wish I will create an ECMA proposal then.
kneczaj commentedon Nov 28, 2025
I want to support this issue. This example is exactly what I think should be possible:
I found the issue by looking for the issue duplicate before filling my ticket about such structure.
I think this example is so natural, that everybody would understand it even with no additional documentation. I do not say to skip it in the docs, just I want to highlight how intuitive this pattern is.
Technically, I would say that this is a mix of inferred type and typing with annotations. When writing this I literally look at this part of TS docs (source):
Comparing to documentation we can say that whole
MyComponentprops are inferred from the destructure statement, howevera, andbhave type annotations.This is just about allowing type annotations inside destructure statement, and applying them to inferred type of whole component props.
The big influence on programming style would be that writing these function signatures would need almost the same effort:
In situation when there is a need to add required argument
cfor first function, due to requirement of having optional args at the end it becomes:As the result all the function calls must be fixed, and if this is a public api of a library, then it implies a major version release.
For the second version the modified signature will become
which needs no additional changes in the rest of the code, and minor version release if it is a lib api.
Due to this advantage, and minimally more effort, I expect
function example({ a: string, b?: string })variant to be used more often. Because of this I expect this little modification of TS to have significant positive impact on velocity and maintenance costs of many projects.I would not only allow this structure in function signatures. Example usages outside a function signature:
This is not so often needed, but I found myself in need of typing the destructure result when
fnhas many alternative signatures, and its result is not correctly inferred from theargs.I want to notice that after this change, these would be equivalents, but I find this fact neutral:
I do not expect the second version to really show up in code as it is simply longer to write.
AlseinX commentedon Nov 28, 2025
This is an ambiguous syntax, for it could also be interpreted as "deconstructing the argument as an object, with its
arenamed tostringand itsbrenamed tonumber"I suggest this:
if the exported variables need to be both annotated and renamed, it turns to be