1

When actions is defined in the object, I want type C to be B, when it's not defined, I want type C to be A, how to implement such type/interface?

interface A {
  label: string
  prop: string
}

interface B {
  label?: string
  actions: string[]
}

// Pseudocode
type C = actions ? B : A

const c1: C = { //  type A
  label: 'c1',
  prop: 'c1',
}

const c2: C = { //  type B
  label: 'c2',
  actions: ['c2'],
}
Wenfang Du
  • 8,804
  • 9
  • 59
  • 90

1 Answers1

1

You'll need a type parameter on C so that it knows what type its dealing with, which for the assignments shown makes C fairly pointless (just use A or B). You also can't infer type arguments in simple assignments like those.

You can with functions, though, so if you're really writing a function and just simplified it for the question (or you're happy to have a pass-through function), you can do this:

type C<T> = T extends { actions: string[] } ? B : A;

function example<T>(data: T): C<T> {
    return data as C<T>;
}

const c1 = example({
    label: "c1",
    prop: "c1",
});
console.log(c1);
//          ^? const c1: A

const c2 = example({
    label: "c2",
    actions: ["c2"],
});
console.log(c2);
//          ^? const c2: B

Playground link

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Wow, thanks! Where is `// ^? const c1: A` this feature documented? It works in vscode too. – Wenfang Du Mar 02 '23 at 13:34
  • @WenfangDu - It's a [twoslash query](https://www.typescriptlang.org/play#handbook-14). Currently only supported in the Playground, but [there's a suggestion](https://github.com/microsoft/TypeScript/issues/52839) to add support for it to what editors get from TypeScript (though there's some question whether TypeScript itself needs to change anything there; it already supports type inlays, so the information is available to the editor if it asks for it). – T.J. Crowder Mar 02 '23 at 13:38
  • @WenfangDu *"It works in vscode too"* Really? It doesn't for me (yet). Maybe I'm behind, what version are you on? I'm on 1.75.1. – T.J. Crowder Mar 02 '23 at 13:39
  • 1
    That's strange, mine is the same version, [sample](https://i.stack.imgur.com/PmKWD.png). – Wenfang Du Mar 02 '23 at 13:43
  • @WenfangDu - Thanks for the screenshot. Maybe you have some extension I don't? (I just upgraded to v1.76.0 and still don't have twoslash queries.) – T.J. Crowder Mar 02 '23 at 13:45
  • 1
    Disabling [this extension](https://marketplace.visualstudio.com/items?itemName=Vue.volar) removed that feature. – Wenfang Du Mar 02 '23 at 13:59
  • Nice debugging @WenfangDu! :-) – T.J. Crowder Mar 02 '23 at 14:01
  • Another thing worth noting is that I also [disabled the vscode builtin typescript plugin](https://i.stack.imgur.com/0sL26.png), and used [this TS server](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) exclusively for all TS files following [this guide](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode). – Wenfang Du Mar 02 '23 at 14:05
  • The real use case is [this](https://i.stack.imgur.com/yp5vM.png), dividing `Column` into two separate types, so I'm not sure if a function can be used here. – Wenfang Du Mar 02 '23 at 14:24
  • @WenfangDu - It depends on why you want to split it, and what you mean by two separate types. Completely separate, or one extending the other, or...? – T.J. Crowder Mar 02 '23 at 14:28
  • Sorry for my description, it's more like `type Column = actions ? ColumnB: ColumnA`, here's the [data structure](https://i.stack.imgur.com/Pv2BH.png), you will understand by seeing this. – Wenfang Du Mar 02 '23 at 14:36
  • Looks like `Column` is just `A | B`? – T.J. Crowder Mar 02 '23 at 15:21
  • I initially thought `type Column = A | B` would suffice, but when accessing `actions` on a column, TS throws `actions` doesn't exist on `A` :(. – Wenfang Du Mar 02 '23 at 15:29
  • 1
    @WenfangDu - That's normal, since it doesn't exist on all types in the union. So you have to use a type guard to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of the particular column you're working with (in this case, perhaps with [an `in` guard](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing)). See [this question's answers](https://stackoverflow.com/questions/43496154/accessing-different-properties-in-a-typescript-union-type) (and the linked docs). – T.J. Crowder Mar 02 '23 at 15:35
  • 1
    Thank you! That was incredibly helpful, I ended up using [`StrictUnion`](https://stackoverflow.com/a/65805753/7881859) to implement [this](https://i.stack.imgur.com/4NzLK.png), and the _"`actions` doesn't exist on `A`"_ issue went away. – Wenfang Du Mar 03 '23 at 05:44