最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - keyof of multiple interfaces - Stack Overflow

programmeradmin1浏览0评论

I would like to have autocompletion for a function that takes a key of an object as a parameter and determines the type of the second parameter based on the key. If I only use a single interface as the source of the key, everything works fine:

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

const foo = <T extends keyof A>(k: T, params: A[T]["params"]) => {
  console.log(k, params);
};

foo("X", {a: "a", b: 1}); // works
foo("Y", {c: 1, d: "a"}); // correct error: Type 'string' is not assignable to type 'number'.

However, as soon as I add a second interface as a possible source, TypeScript tells me that I can no longer use the generic type as an index:

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

interface B {
  Z: {params: {e: string; f: string}};
}

const foo = <T extends keyof A | keyof B>(k: T, params: A[T]["params"] | B[T]["params"]) => {
  console.log(k, params);
};
// errors:
// * Type 'T' cannot be used to index type 'A'.
// * Type '"params"' cannot be used to index type 'B[T]'.

How do I have to type params of the foo function to make it work?


UPDATE

For the example above, it would be sufficient to combine A & B into one interface (thanks @jcalz). But for my use case this is not enough. I have a new example here, which hopefully shows more clearly what I want.

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

interface Props {
  [key: string]: {params: any};
}

type AdditionalProps = Props & {[K in keyof A]: never};

interface B extends AdditionalProps {
  Z: {params: {e: string; f: string}};
}

class C<AP extends AdditionalProps> {
    // error: Type 'T' cannot be used to index type 'A'.
    foo = <T extends keyof A | keyof AP>(k: T, params: A[T]["params"] | AP[T]["params"]) => {
    console.log(k, params);
  };
}

const c = new C<B>();

// works - without autocompletion for parameter `k`, but for `params` if `k` is a key of interface `A`
c.foo("X", { a: "", b: 1 })

// No autocomplete for `k`. If `"Z"` is entered for `k`, `params` has type `never`
c.foo("Z", ) 

I would like to have autocompletion for a function that takes a key of an object as a parameter and determines the type of the second parameter based on the key. If I only use a single interface as the source of the key, everything works fine:

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

const foo = <T extends keyof A>(k: T, params: A[T]["params"]) => {
  console.log(k, params);
};

foo("X", {a: "a", b: 1}); // works
foo("Y", {c: 1, d: "a"}); // correct error: Type 'string' is not assignable to type 'number'.

However, as soon as I add a second interface as a possible source, TypeScript tells me that I can no longer use the generic type as an index:

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

interface B {
  Z: {params: {e: string; f: string}};
}

const foo = <T extends keyof A | keyof B>(k: T, params: A[T]["params"] | B[T]["params"]) => {
  console.log(k, params);
};
// errors:
// * Type 'T' cannot be used to index type 'A'.
// * Type '"params"' cannot be used to index type 'B[T]'.

How do I have to type params of the foo function to make it work?


UPDATE

For the example above, it would be sufficient to combine A & B into one interface (thanks @jcalz). But for my use case this is not enough. I have a new example here, which hopefully shows more clearly what I want.

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

interface Props {
  [key: string]: {params: any};
}

type AdditionalProps = Props & {[K in keyof A]: never};

interface B extends AdditionalProps {
  Z: {params: {e: string; f: string}};
}

class C<AP extends AdditionalProps> {
    // error: Type 'T' cannot be used to index type 'A'.
    foo = <T extends keyof A | keyof AP>(k: T, params: A[T]["params"] | AP[T]["params"]) => {
    console.log(k, params);
  };
}

const c = new C<B>();

// works - without autocompletion for parameter `k`, but for `params` if `k` is a key of interface `A`
c.foo("X", { a: "", b: 1 })

// No autocomplete for `k`. If `"Z"` is entered for `k`, `params` has type `never`
c.foo("Z", ) 
Share Improve this question edited Feb 13 at 8:02 J. Doe asked Feb 10 at 16:03 J. DoeJ. Doe 216 bronze badges 3
  • What you're asking seems to be equivalent to just a single interface with the props from both A and B. That is, if you write (A & B)[T]["params"] like this playground link shows it would work. Does that meet your needs? (Pls check thoroughly against use cases) If so I'll write an answer; if not, what am I missing? – jcalz Commented Feb 10 at 16:26
  • @jcalz Thanks for the answer. You're right, that's enough for this simple example. Unfortunately, my use case is a bit more complex. I'll update my request with a more suitable example. – J. Doe Commented Feb 11 at 6:17
  • 1 Answers don't belong the question; you should either add a new answer post below, or edit the existing answer with the relevant added information. – jcalz Commented Feb 11 at 17:31
Add a comment  | 

1 Answer 1

Reset to default 1

Given that you could use or couldn't the same keys you could utilize some conditional typing:

Playground

interface A {
  X: {params: {a: string; b: number}};
  Y: {params: {c: number; d: number}};
}

interface B {
  X: {params: {a: number; b: number}};
  Z: {params: {e: string; f: string}};
}


type ChooseByKey<K extends any> = K extends keyof A ? K extends keyof B ? (A|B)[K] : A[K] : K extends keyof B ? B[K]: never
const foo = <K extends keyof A | keyof B>(k: K, params: ChooseByKey<K>['params']) => {
  console.log(k, params);
};

foo('X', {a: '1', b: 1});
foo('X', {a: 1, b: 1});
foo('Y', {c: 1, d: 1});
foo('Y', {c: 1, d: 's'}); // error
发布评论

评论列表(0)

  1. 暂无评论