Ascendant
Ascendant

Reputation: 1004

Is there a way (or workaround) for destructuring of typescript types/interfaces?

Let's say I have a class with a lot of type parameters:

class BaseClass<T extends T1, U extends U1, V extends V1, /* etc. */ > 

Is there a way to create a single type argument that could allow for "spreading" or "destructuring" along the lines of what you can do with objects in ES6?

So instead of needing to implement subclasses as

class FooClass extends BaseClass<TFoo, UFoo, VFoo, /* etc. */ >

I could set all the type arguments and pass them in one go

// example of what I'd like to be possible:
metatype FooTypes = <TFoo, UFoo, VFoo, /* etc. */>
class FooClass extends BaseClass<...FooTypes>

The reason I'm hoping to do something like this is that I've got different objects that are responsible for different properties of entities like "notes" and "comments" that have nearly a dozen associated types (e.g. local shape, API response, shape inside 3rd-party cache, etc.) but not every object that interacts with them needs to work with every single one of those types.

What I want to do is pass a single reference e.g. "NoteTypes", "CommentTypes" that essentially means "You can map whatever type parameters you need against this."

The problem I'm describing would seem to be best solved by "type destructuring," but this GitHub issue would seem to indicate that this isn't possible yet.

In lieu of actual type destructuring, what would be the best way to approach this?

Upvotes: 0

Views: 255

Answers (1)

jcalz
jcalz

Reputation: 328453

There's no such syntax to perform object destructuring at the type level as in microsoft/TypeScript#13135 or array destructuring at the type level as in microsoft/TypeScript#5453. But luckily you can perform object/tuple type indexing, via lookup types. If you have an object type type M = {foo: F, bar: B} where F and B are existing types, then you can recover F by looking up "foo" in M: M["foo"]. And B is M["bar"].

Given that, you could decide to package your T, U, V, etc as a single map of types M like this:

class BaseClass<M extends { T: T1, U: U1, V: V1 }> { /* ... */ }

If you need to refer to T, U, and V, types you can look them up:

class BaseClass<M extends { T: T1, U: U1, V: V1 }> {
    constructor(public t: M["T"], public u: M["U"], public v: M["V"]) {
    }
}

As an example here, BaseClass<M> has a constructor taking parameters of the old T, U, and V types.


Then your subclassing convenience code looks like:

type FooTypes = { T: TFoo, U: UFoo, V: VFoo }
class FooClass extends BaseClass<FooTypes> { }

This should work, although it would be more convenient if there were a simple syntax to destructure instead of using lookups. Right now you could do something like

    type T = M["T"];
    type U = M["U"];
    type V = M["V"];

but that is fairly verbose and only works in global or function scope, not within a class (see microsoft/TypeScript#7061) so in practice I'd probably just use M["T"] and M["U"] etc wherever I formerly used T and U.


Okay, hope that helps; good luck!

Playground link to code

Upvotes: 1

Related Questions