Open Richiban opened 5 years ago
Duplicate/related to #7576, #29019
I know expression level syntax is a no-no, but how about:
const MyComponent = ({ ... } : { a : string, b : number }) => {
// ...
}
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:
const MyComponent = ({ a:aa, ... } : { a : string, b : number }) => {
}
There is a reason this issue keeps poping up. The current solution is painful.
I agree it's a duplicate but it really does suck. We should try again.
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.
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.
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.
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.
While I also think @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:
// The Javascript bit
// --------
const myFunction({ a, b } : SomeType) { ... }
// ----------
// The Typescript bit
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.
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.
// rename, no type annotations
const HelloWorld = ({name : newName }) => {
}
// rename, with annotation
const HelloWorld = ({name : newName : string }) => {
}
// empty rename, with annotation
const HelloWorld = ({name :: string }) => {
}
// empty rename, with annotation, and default
const HelloWorld = ({name :: string = "default" }) => {
}
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:
type FooBar = { foo: string; bar: number }
const HelloWorld = ({ a, b:{ foo: fooLocal, bar: barLocal }: FooBar }) => {
}
const HelloWorld = ({ a, b::FooBar }) => {
}
const HelloWorld = ({
a: number,
b: {
foo:fooLocal:string,
bar:barLocal:string
}
}) => {
}
@dragomirtitian
Let's not consider :: as a new way to introduce a type annotation, but rather an empty rename.
Works for me! π
@dragomirtitian Good point!
Were angle brackets considered? https://github.com/microsoft/TypeScript/issues/34748
Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}: {foo: string, bar: number, baz: boolean})
has a lot of duplicated information.
DRY?
On 9 Dec 2019, at 21:03, Chris Frolik notifications@github.com wrote:
Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}): {foo: string, bar: number, baz: boolean}) has a lot of duplicated information.
β You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/microsoft/TypeScript/issues/29526?email_source=notifications&email_token=ABQZHXW5ME4SAYR634QZ7STQX2XCVA5CNFSM4GRUGZRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEGKWCEA#issuecomment-563437840, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQZHXWA7OFF26Q7FGJFUTLQX2XCVANCNFSM4GRUGZRA.
Not really related, but it'd be much more easier if object destructuring alias uses as
instead of :
, just like how import
does.
@pynnl that ship sailed a long time ago unfortunately.
TypeScript really needs this! I can't think of a more annoying thing than this. Any syntax is okay, as long as it gets implemented. Please. This has been discussed for such a long time. There are a lot of options that don't collide with the ES spec.
I'd like to propose a variant of the multi-colon syntax:
{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }
The idea is to keep the double colon, even when remapping the name, so that the type is always identified by the colon-block (or colon-square) operator ::
. The point of this subtlety is to make it easy for the reader to visually parse where the name ends and where the type begins. This is achieved through the use of a different operators rather than being purely syntax-based.
Here is another idea of syntax, which resembles the "Auto spread" proposition of @dragomirtitian: {...}: { x: number }
. It actually extends the syntax of "Interface spread to variable" proposed by @antanas-arvasevicius in #14856, adding support for renaming variables:
let ...{ x: number } = v // x is now available
let ...{ x: number = 123 } = v
let ...{ xin xout: number } = v // xout is now available
let ...{ xin xout: number = 123 } = v
let ...{ left { data dataLeft: string }, right { data dataRight: string } } = v
// dataLeft and dataRight are now avaliable
Example with a function, a let together with the rest
parameter:
const formatSidedData = (...{ left { data a: string }, right { data b: string } }) => `${a}<->${b}`
let ...{ person { name: string, age: number }, ...metadata: Record<string, string> } = getInfo()
Note: the ellipsis ...
"type annotated destructuring-declaration" can only be used at the top level, either in a const
, let
or var
list of variable declarations, or among the arguments of a function. I suggest a standard destructuring-declaration should not be allowed to use ...
inside it to create a zone of "type annotated destructuring-declaration".
One of the unique nice properties only found in @dragomirtitian proposal, that of @antanas-arvasevicius and this one is that in the simple case --without renaming--, the syntax used is just the usual syntax for interfaces. This covers the use case of easily transforming a type annotated destructuring into an interface (by unedited copy-paste). This also applies to copy-pasting an interface to a "type annotated destructuring".
Below is an example to clarify.
Let there be a function magnitude
, computing the length of a vector. Using my "Extended interface spread to variable" syntax, we can write this:
function magnitude(...{ x: number, y: number }) {
return (x ** 2 + y ** 2) ** 0.5;
}
We then realize we want to name the interface. It is easy to just copy-paste it to a named type:
interface Point { x: number, y: number }
function magnitude({ x, y }: Point) {
return (x ** 2 + y ** 2) ** 0.5;
}
If you use Prettier, it will automatically reformat the interface to this:
interface Point {
x: number;
y: number;
}
Doing the opposite change (going back) is also easy, though you may need to remove some semicolons and add commas:
function magnitude(...{ x: number, y: number }) {
return (x ** 2 + y ** 2) ** 0.5;
}
This syntax can be used without any issue with the traditional ellipses for varargs functions:
function join(...{ begin: string, sep: string, end: string }, ...parts: string[]) {
return begin + parts.join(sep) + end;
}
let { a } = argv
" (2019-04)Note:
- ultimately, every new syntax added is added complexity, for the user to learn and recognize, and for the compiler code base to maintain, so usually we try to limit complexity unless the value warrants it. and for destructuring, the syntax is already complex to start with, and we did not want to add yet another layer on top.
-- seconded by @DanielRosenwasser
Here are a few propositions I found whose syntax cannot be used.
as_remap_and_colon_type
#7576 @odiak{ x: number }
{ xin: number = 123 }
{ xin as xout: number }
{ xin as xout: number = 123 }
The first two lines are valid JS and TS. This syntax is incompatible.
(This is about formatting recommendations and auto-formatting)
double_colon.sparse1
{ x:: number }
{ xin:: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
double_colon.sparse2
{ x :: number }
{ xin :: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
double_colon.compact2
(formatting variant){ x::number }
{ xin::number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
double_colon.compact4
(formatting variant){ x::number }
{ xin::number = 123 }
{ xin:xout:number }
{ xin:xout:number = 123 }
current_grouped
{ x }: { x: number }
{ xin = 123 }: { xin: number }
{ xin: xout }: { xin: number }
{ xin: xout = 123 }: { xin: number }
current_split_and_named
interface Prop { x: number }
{ x }: Prop
interface Prop { x: number }
{ xin = 123 }: Prop
interface Prop { xin: number }
{ xin: xout }: Prop
interface Prop { xin: number }
{ xin: xout = 123 }: Prop
All these are proposals for "Complete type annotated destructuring".
(order attempts to group propositions sharing similar traits)
as_type
[#7576 @osi-oswald @rauschma, #30574 @AlseinX, #31049 @bluelovers]{ x as number }
{ xin: xout as number = 123 }
angle_bracket_before
([#1912 @NN---] but ambiguous){ <number>x }
{ <number>xin: xout = 123 }
angle_bracket_after_remap
[#34748 @danm]{ x<number> }
{ xin: xout<number> = 123 }
auto_spread
[#29526 @dragomirtitian]{...}: { x: number }
{ xin = 123 }: { xin: number? }
{ xin: xout }: { xin: number? }
{ xin: xout = 123 }: { xin: number? }
sprea_with_remap
[#29526 @mathieucaroff], ([#14856 @antanas-arvasevicius] but incomplete)...{ x: number }
...{ xin xout: number = 123 }
double_colon
[#240 @RyanCavanaugh @fdecampredon @mpawelski], [#29526 @dragomirtitian] ([#5034 @schungx], [#29526 @Richiban] but ambiguous){ x:: number}
{ xin: xout: number = 123 }
colon_square
[#29526 @mathieucaroff]{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }
concatenation_after_remap
[#13471 @mightyiam]{ x number }
{ xin: xout number = 123 }
concatenation_before_colon
[#7576 @MichaelBuen]{ x number }
{ xin number: xout = 123 }
parentheses
[#7576 @rauschma]{ x: (number) }
{ xin: (xout: number) = 123 }
as_type
{ xin: xout as number = 123 }
Cons
as T
is currently used for type assertion rather than type annotation; this will cause confusionPros
as T
already means "type"; users will feel more familiar with this new syntaxangle_bracket_before
{ <number>xin: xout = 123 }
Cons
<T>x
is currently used for type assertion rather than type annotation; this will cause confusionPros
<T>x
already means "type"; users will feel more familiar with this new syntaxangle_bracket_after_remap
Cons
x<T>
looks like A<T>
, the syntax for type parameter; this overload can feel strangePros
x<T>
feels like Typescript since it's similar to an existing syntaxauto_spread
{ xin: xout = 123 }: { xin: number? }
, but {...}: { x: number }
in the simple case
Cons
let {...}: { x: number } = a
might let the user believe that let {...} = a
exists and is a meaningful unpacking strategy, but it is not since the { ... }
syntax only exists with type annotationPros
spread_with_remap
Cons
let ...{ x: number } = a
might let the user believe that let ...T
exists and is a meaningful unpacking strategy, but it is notPros
double_colon
{ xin: xout: number = 123 }
Cons
Pros
- there is value in consistency that types always come after a :. adding another operator would increase complexity.
- we have the : domain almost officially reserved for types.
colon_square
{ xin: xout:: number = 123 }
(disallowing spaces between the two colon)
Cons
Pros
concatenation_after_remap
{ xin: xout number = 123 }
Cons
Pros
concatenation_before_colon
{ xin number: xout = 123 }
Cons
{ xin number: xout }
, the type number
appears before the variable xout
; which is never done in TypeScriptPros
parentheses
{ xin: (xout: number) = 123 }
Cons
{ xin: (number) }
Pros
Here is my order of preference on these 10 alternatives, from most wanted to least wanted:
My "approval threshold" separates the options I would vote for from those I would not in the event of a vote using approval voting.
I'm interested in your own preferences, especially if you believe debating can lead to a consensus. In case debating fails to lead to a consensus, or debating is not attempted, I believe a vote (using approval voting) would still be a satisfying outcome.
Would love to be able to type named parameters without having to duplicate the name of the parameter! In React code bases basically every component has a unique interface so you end up doing a lot of extra typing. I like the double colon, anybody who works in the TypeScript codebase have a feeling for how hard this would be to implement?
I think as_type is the best proposal here, as the "as" keyword is not used in structuring/destructuring, and doesn't produce ambiguous syntax. Not to mention, it produces a lot more readable code.
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:
- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- [x] This feature would agree with the rest of TypeScript's Design Goals.
I vote for @Richiban's double colon syntax
Would auto_spread work with interface types e.g. {...}: InterfaceType
? Seems a bit much to have a function's local variables be defined arbitrarily far away.
As I imagine it, {...}: InterfaceType
would be an invalid syntax. auto_spread would be a rigid syntax with four braces in it: {...}: {}
.
angle_bracket_after_remap
{ x<number> }
{ xin: xout<number> = 123 }
angle_bracket_before
{ <number>x }
{ <number>xin: xout = 123 }
as_type
{ x as number }
{ xin: xout as number = 123 }
auto_spread
{...}: { x: number }
{ xin: xout = 123 }: { xin: number? }
double_colon
{ x:: number}
{ xin: xout: number = 123 }
colon_square
{ x:: number }
{ xin: xout:: number = 123 }
concatenation_after_remap
{ x number }
{ xin: xout number = 123 }
concatenation_before_colon
{ x number }
{ xin number: xout = 123 }
parentheses
{ x: (number) }
{ xin: (xout: number) = 123 }
spread_with_remap
...{ x: number }
...{ xin xout: number = 123 }
I've posted each of the 10 valid syntax proposals in separate messages. In 80 days (two months and month and 19 days), on Sunday the 6th of June, I will count and post in this issue the number of THUMBS_UP
reaction on each alternative.
I love typescript and I love react, but trying to write my functional react components with typed props is currently the biggest pain point. This problem needs a solution.
A lot of the proposals above have flaws which I believe is a result of trying to combine the features of interfaces (name, type) and destructuring (name, remap, default) into syntax as terse as possible.
Most react developers don't particularly care that our props come to us as an object; that's just how the react api gives them to us. We don't need all of the features of objects, and we only use destructuring syntax in order to emulate named parameters.
Given that, in order to solve this problem, I think we could find a more elegant solution if we think about this a little differently - we don't want to extend destructuring syntax, we want (something which emulates) named parameters. Meaning an alternative function signature syntax would be a better solution for react developers, especially if it doesn't need to support remapping.
One approach could be borrowing the syntax from declaring class members:
function FooBar({ foo: string; bar: string = 'baz'; }) {
return <span>{foo} {bar}<span>;
}
This may look similar to existing destructuring syntax but is subtly different, in that it uses semicolons to delimit fields instead of commas - a win-win as it should be instantly familiar to developers, as well as sufficiently unique enough to not cause a syntax collision.
A more radical alternative could be the addition of a named
keyword to the function signature, which would imply that the function accepts only one argument, which is an object with the specified fields, in the same way that the async
keyword implies that the return value will always be wrapped in a promise.
function FooBar named (foo: string, bar: string = 'baz') {
return <span>{foo} {bar}<span>;
}
These would both likely be considered out-of-scope and too big of a departure from existing ES syntax, but this is a common enough use case that I think some special consideration is warranted.
Any update on this?
After giving it some thought, I think auto spread would be a pretty good solution to the problem! When looking at the use case, it makes perfect sense. When you don't want specify the type, but infer it from the spreading, then you always want to access all of the properties of the resulting type. Then, why not just autospread them all anyway. This way we wouldn't even have to introduce a new method to "construct" types.
I would be fine with other solutions as well, though. I just think it would be a quick, easy and reasonable solution to this problem.
parentheses
{ x: (number) } { xin: (xout: number) = 123 }
I quite like this one in the simpler case. But why aren't the parentheses around "number" in the renaming case too? Like this:
{ xin: xout: (number) = 123 }
Would that clash with something else somehow? I want it that way, anyhow. It feels unnatural to me to put parentheses around the new name, whereas putting them around the type feels like a perfectly normal way to refer to the type. Because, I mean, it IS that. You can do this, after all:
type NumberOrString = number | string;
let n: (((((((((NumberOrString))))))))) = x < 3 ? 3 : "three";
Of course, the "xin: xout" part of it would look weird to a Typescript programmer, but whatever - that's just a consequence of choosing the colon as a type annotation operator. The colon is used for renaming and type annotation both, and solving that problem is outside the scope of this issue. So I think we should accept the weirdness of "xin: xout: (number)". Unless it causes some other unacceptable problem I haven't thought of, I mean.
Come on, vote for this one! Unless you like something else better. (I voted for double_colon above as well!)
@mjwach, I'm adding your proposal to the contest.
parentheses-around-type
{ x: (number) }
{ xin: xout: (number) = 123 }
I already spend my day trying to coax JS devs to adopt TS. IMO, some of these syntaxes are quite obstructive to TS adoption; it's very painful to teach ":
here, but ::
there", or ":
here, but <...>
there". These edge cases might be acceptable for someone who's already a TS professional, but I wouldn't wish it on a new learner. Please think of the n00bs!
I think there should be consideration for how the proposals deal with optional properties (which surely isn't so rare). Here's my best guesses (and opinion):
// angle_bracket_after_remap => π‘ doesn't look like TS
{ x<number?> }
{ xin: xout<number?> = 123 }
// angle_bracket_before => π‘ doesn't look like TS
{ <number?>x }
{ <number?>xin: xout = 123 }
// as_type => π‘ doesn't look like TS
{ x as number? }
{ xin: xout as number? = 123 }
// auto_spread => β
intuitive
{...}: { x?: number }
{ xin: xout = 123 }: { xin?: number }
// double_colon => β slightly ambiguous
{ x?:: number}
{ xin: xout?: number = 123 }
// ...or...
{ x:?: number}
{ xin: xout?: number = 123 }
// colon_square => π‘ more new syntax
{ x?:: number }
{ xin: xout?:: number = 123 }
// concatenation_after_remap => π‘ doesn't look like TS
{ x number? }
{ xin: xout number? = 123 }
// concatenation_before_colon => β confusing
{ x number? }
{ xin number?: xout = 123 }
// parentheses => β ambiguous
{ x: (number?) }
{ xin: (xout: number?) = 123 }
// ...or...
{ x?: (number) }
{ xin: (xout?: number) = 123 }
// parentheses-around-type => β ambiguous
{ x: (number?) }
{ xin: xout: (number?) = 123 }
// ...or...
{ x?: (number) }
{ xin: xout?: (number) = 123 }
// spread_with_remap => β
intuitive
...{ x?: number }
...{ xin xout?: number = 123 }
Are pre-existing types out of scope (for now)? Either way I think the syntax choice should be future-proof:
// β Not sure how...
// angle_bracket_after_remap
// angle_bracket_before => doesn't look like TS
// as_type => doesn't look like TS
// double_colon => awkward
// colon_square => awkward
// concatenation_after_remap => doesn't look like TS
// concatenation_before_colon => confusing
// parentheses => ambiguous
// parentheses-around-type => ambiguous
???
// auto_spread => β
intuitive
{...}: MyInterface
{ xin: xout = 123 }: MyInterface
// spread_with_remap => π‘ not sure how to rename
...MyInterface
??? for renaming
Finally, if pre-existing types are supported, the auto_spread syntax looks capable of partial renames:
// auto_spread => β
awesome
{...}: MyInterface
{ xin: xout = 123 }: MyInterface
{ xin: xout = 123, ... }: MyInterface
{ xin: xout = 123, ...rest }: MyInterface
Isn't there any external tool/plugin/transformer which enables any of these suggestions?
+1 for the spread_with_remap option
I like that I can imagine what it does at first sight, unlike the other options (with the exception of auto_spread, which is similar, but more verbose)
About 80 days ago, I said:
I've posted each of the 10 valid syntax proposals in separate messages. In 80 days (two months and month and 19 days), on Sunday the 6th of June, I will count and post in this issue the number of
THUMBS_UP
reaction on each alternative.
Here is are the counts, in alphabetical order:
2 angle_bracket_after_remap
0 angle_bracket_before
10 as_type
9 auto_spread
18 colon_square
0 concatenation_after_remap
0 concatenation_before_colon
13 double_colon
1 parentheses
3 parentheses_around_type
6 spread_with_remap
The top 4 proposals are the following:
Here are their descriptions:
colon_square
(18){ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }
double_colon
(13){ x:: number }
{ x:: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
as_type
(10){ x as number }
{ x as number = 123 }
{ xin: xout as number }
{ xin: xout as number = 123 }
auto_spread
(9){...}: { x: number }
{ x = 123 }: { x: number }
{ xin: xout }: { xin: number }
{ xin: xout = 123 }: { xin: number }
I understand, however, in the case that vote is not binding, I want to emphasize that @charles-allen comment should be taken into account. intuitiveness is important, and if we can use syntax we are already used to instead of having to learn a new one (double colon), it would be better.
IMO
as
syntax is not acceptable: It might conflict with pattern match syntax (@Jack-Works knows more about this).::
and :
is not acceptable: It not pure type level syntax. Seems TypeScript team are very careful about new syntax. PS. And might conflict with proposal-extensions (@hax knows more about this).
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:
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:but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:
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:
Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:
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:
Checklist
My suggestion meets these guidelines: