Skip to content

Support for type destructuring #13135

Open

Description

TypeScript Version: 2.1.4

Code

import { MyNamespace } from 'my-package';

// importing Class objects:
const { NavLink, NavGroup } = MyNamespace; // VALID

// if we want the types as well:
type { NavLink, NavGroup } = MyNamespace; // NOT VALID

// So we have to go manual: 
type NavLink = MyNamespace.NavLink;  // VALID
type NavGroup = MyNamespace.NavLink;  // VALID

Expected behavior:
Being able to use destructuring for types same as we use for objects.

Actual behavior:
Can only use destructuring for objects.

Activity

  1. aluanhaddad commented on Dec 23, 2016

    @aluanhaddad
    Contributor

    If this is supported, it seems like it should work for the import statement as well so that one may acquire both the type and value space meanings if desired.

    import { NavLink, NavGroup } = MyNamespace; // NOT VALID
  2. shlomiassaf commented on Dec 31, 2016

    @shlomiassaf
    Author

    Another annoying thing is the expected variable-declaration to have a typedef lint rule.

    We had to suppress it since const { NavLink, NavGroup } = MyNamespace; had no type...

    so import { NavLink, NavGroup } = MyNamespace; will solve that.

  3. cchamberlain commented on Feb 1, 2017

    @cchamberlain

    This would be a great feature to have. Looks like general users are struggling with this as well: http://stackoverflow.com/questions/31394725/typescript-destructuring-alias-import

  4. cchamberlain commented on Feb 1, 2017

    @cchamberlain

    An interim that might make sense on the way to type alias destructure might be support for type aliases for namespaces:

    import { MyNamespace } from 'my-package';
    
    type N = MyNamespace;
    
    type NavLink = N.NavLink; 

    Logic for this is that ES supported the above assignment of variables prior to being able to destructure.

  5. tjpalmer commented on Oct 10, 2017

    @tjpalmer

    I was hitting this today. Would be fantastic.

  6. djansyle commented on Sep 24, 2018

    @djansyle

    Encountered this scenario this day.

  7. bySabi commented on Jan 22, 2019

    @bySabi

    I had been using typescript import in place of ES6 imports in order of be more explicit on what is a type a what is javascript.

    Ex:

    import { Foo, Bar } from './classes';  // Javascript classes
    
    type FooType = import('./types').FooType;
    type BarType = import('./types').BarType;
    
    

    Would be nice to have destructuring for type imports just like ES6 imports.
    Ex:

    import { Foo, Bar } from './classes';  // Javascript classes
    
    type { FooType, BarType } = import('./types');
    

    Any Roadmap for this feature or never be?

  8. OliverJAsh commented on Jan 22, 2019

    @OliverJAsh
    Contributor

    Aliasing is problematic when the type to be imported has generics:

    // ./lib.ts
    export type Foo<T extends string> = { foo: T }
    
    // ./index.ts
    // ERROR
    type Foo = import('./lib').Foo;
    
    // We have to repeat generics:
    type Foo<T extends string> = import('./lib').Foo<T>;
    
    // Separately: expected error, but got none. Bug?
    type Foo<T> = import('./lib').Foo<T>;
  9. bySabi commented on Jan 23, 2019

    @bySabi

    your are right!

    Probably, aliasing with generics is what stop any move on this.

  10. ashmind commented on Apr 1, 2020

    @ashmind

    Would be cool to destructure tuple types as well:

    type [NameType, ValueType] = Parameters<typeof methodWithNameAndValue>;
    
  11. seeruk commented on Jun 14, 2020

    @seeruk

    Would love a way to get around this; one practical example where I'm experiencing this is when using the output from protobuf.js's pbjs and pbts tools. They use namespaces, and you can end up with a huge list of imports at the top of the file that could be a lot more compact.

  12. monolithed commented on Jul 22, 2020

    @monolithed

    Some examples:

    function foo(x: number, y: number): number {
       return x + y;
    }
    
    type [X, Y] = Parameters<typeof foo>;
    
    const x:X = 1;
    const y:Y = 3;
    
    foo(x, y); 
    type Foo = {
       x: number;
       y: number;
    }
    
    type {X, Y} = Foo;
    
    const x:X = 1;
    const y:Y = 3;

    Sheetal Nandi (@sheetalkamat), maybe you know, is there an active discussion of similar feature?

  13. JCMais commented on Feb 2, 2021

    @JCMais

    I've been hitting this limitation.

    With a monorepo it's hard to use types that are not exported by the main file of an internal package.

  14. voodooattack commented on Mar 29, 2021

    @voodooattack

    Ran into this today. I'm writing a tool that exports tables/types from PostgreSQL to TypeScript definitions and I'm forced to generate namespaces (in order to avoid name collisions across schemas).

    I feel that namespaces are not first class citizens because of this limitation making them too unwieldy.

    I'd love to be able to do:

    /// this:
    import { app_public } from "./types";
    import { user, user_info } = app_public;
    
    /// or even better, this:
    import { app_public: { user, user_info } } = from "./types";

    Instead of what I'm doing right now:

    import { app_public } from "./types";
    import user = app_public.user;
    import user_info = app_public.user_info;
  15. Nokel81 commented on May 26, 2021

    @Nokel81

    Another option would be to allow the following syntax:

    namespace Foo = MyNamespace.Foo;

    This would shorten MyNamespace.Foo.MyType to just Foo.MyType

  16. Bagel03 commented on Sep 11, 2021

    @Bagel03

    This would make it so much easier to get types from namespaces, especially in instances where you are exporting a class and destructing it only grabs the value. (Ran into this today)

  17. shellscape commented on Jan 20, 2022

    @shellscape

    5 Years

    That's how long this has been open. Is there any possibility of comment from maintainers as to why this hasn't been addressed?

  18. RyanCavanaugh commented on Jan 20, 2022

    @RyanCavanaugh
    Member

    Andrew Powell (@shellscape) pretty much everything TypeScript could possibly ever do was suggested at one point within the first two years of its release, which was about 10 years ago. Needless to say, in that timeframe, we have not done every possible thing, nor could/should we have. C++ is nearing 40 years old at this point and is still adding new features. You'll need to readjust expectations around how fast a language can responsibly add features, unfortunately.

  19. shellscape commented on Jan 20, 2022

    @shellscape

    Ryan Cavanaugh (@RyanCavanaugh) I think you're conflating expectations of execution with expectations of comment. it is not unreasonable to suggest that an issue 5 years old should get at least one comment from a maintainer.

    I can't figure out if it's unfortunate or hilarious that the only comment this issue received from a maintainer, especially the Development lead for the TypeScript team at Microsoft. was to scold a user who pointed out the lapse of activity by the maintainers of the project on the issue. Whether or not you feel my cheeky reply was in poor taste, that's a bad look.

  20. RyanCavanaugh commented on Jan 20, 2022

    @RyanCavanaugh
    Member

    That's valid, and I'm not trying to scold you. We have thousands of open suggestions and if we were going around keeping every suggestion "fresh", we'd do nothing else.

    Anyway, concerns I see here:

    • Every feature starts at minus 100 and this syntax doesn't enable you to do anything you couldn't do before; it's purely sugar (sweet as that might be)
    • There might be better use of this syntactic space in the future; "using" it to do destructuring seems like the most logical thing but why close it off now?
    • Very strong problems when we think about how namespacing / member access works here.

    To expand on the latter, a few different examples were cited in-thread

    // Set 1
    type [NameType, ValueType] = Parameters<typeof methodWithNameAndValue>;
    type Foo = {
       x: number;
       y: number;
    }
    
    type {X, Y} = Foo;
    
    // Set 2
    type { FooType, BarType } = import('./types');

    Notably, Set 1 and Set 2 propose different behaviors for how this should work. In Set 1, the requested operation is T["n"] (property lookup), in Set 2, the requested operation is T.n (namespace access). We can't pick and choose here because for T and n, there might be meanings for both.

  21. RyanCavanaugh commented on Jan 20, 2022

    @RyanCavanaugh
    Member

    OT: Apologies that I came off that way! "This has been open X years" is a frequent form of unproductive complaining and it sets me on edge whenever I see it, but your request was valid. I can do better and will try to.

  22. shellscape commented on Jan 20, 2022

    @shellscape

    Both replies are very much appreciated.

  23. voodooattack commented on Jan 21, 2022

    @voodooattack

    Andrew Powell (@shellscape) pretty much everything TypeScript could possibly ever do was suggested at one point within the first two years of its release, which was about 10 years ago. Needless to say, in that timeframe, we have not done every possible thing, nor could/should we have. C++ is nearing 40 years old at this point and is still adding new features. You'll need to readjust expectations around how fast a language can responsibly add features, unfortunately.

    Hey, just wanted to say we appreciate all the hard work you guys are doing! There's no place for negativity in open source development. Nobody has the right to throw around blame. That's a dangerous mindset and anyone wanting a feature bad enough to complain about it can add it themselves and make a PR. (or even make their own fork if there's a disagreement)

    Notably, Set 1 and Set 2 propose different behaviors for how this should work. In Set 1, the requested operation is T["n"] (property lookup), in Set 2, the requested operation is T.n (namespace access). We can't pick and choose here because for T and n, there might be meanings for both.

    I propose a disambiguation syntax for this:

    // Set 2
    type { namespace Foo: { X, Y }, BarType } = import('./types');

    Here we're explicitly stating that we want to destructure the members of Foo as a namespace and not as a type. TypeScript can expect Foo to be a namespace and throw an error if it isn't.

  24. somebody1234 commented on May 3, 2022

    @somebody1234

    re: Set 1 - I believe it is not needed whatsoever since what it's really doing is treating a type as a namespace. Destructuring types is a mistake - the only reason one should use the property access operator is for complex generic types, and well... you can't use that syntax in generic types.

    ... well actually, multiple property access is already possible in generic types so that shouldn't be an issue.

    re: "it's purely sugar" - sure. but it's extremely useful sugar that removes a ton of visual clutter.

    edit: elaborating on "types shouldn't be namespaces" - I do think this is strongly support by the fact:

    • type members can be manipulated (listed, iterated over, mapped over)
    • the syntax for accessing a type member is "second-class" - Foo['a'] rather than Foo.a - the latter of which matches property access syntax for values, which IMO makes it more intuitive - hence, "first-class".
  25. somebody1234 commented on May 3, 2022

    @somebody1234

    Anyway, at the very least I think getting something like:

    import { namespace a.b.c.Foo as F } from './foo';

    should be relatively unambiguous, and there should be zero problems with "better use of this syntactic space" here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions