打字稿:如何让第二个参数类型依赖于第一个参数值

时间:2021-07-29 08:38:20

标签: typescript

我想创建一个通用方法,将一个预定义的字符串作为第一个参数,将一些数据作为第二个参数。我希望将“数据”描述为带有这些预定义字符串键的接口

type MyKeys = 'lorem' | 'ipsum' | 'dolor';

interface DataParams extends Record<MyKeys, any | undefined> {
    lorem: { year: number };
    ipsum: { to: string };
}

myCoolGeneric('lorem', {year: 2021}); // ok
myCoolGeneric('ipsum', {year: 2021}); // typescript error

所以我可以为它们指定多组键和不同的可用参数。并且打字稿将检查该方法是否使用正确的参数调用,具体取决于哪个键用作参数

我应该如何声明 myCoolGeneric?有可能吗?或者也许还有其他一些方法可以实现这种行为?

我尝试了以下代码,但它给出了错误,即键被用作类型中的值

const myCoolGeneric = <P extends Record<string, any | undefined>>(key: keyof P, data P[key]) => { ... }

1 个答案:

答案 0 :(得分:1)

谢谢@kaya3!

更新

如果你有不同的允许参数组合,你可以产生函数重载。

type MyKeys = 'lorem' | 'ipsum' | 'dolor';

interface DataParams {
  lorem: { year: number };
  ipsum: { to: string };
}

// credits goes to https://stackoverflow.com/a/50375286
// function intersection produces - function overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Values<T> = T[keyof T]

/**
 * Generate all possible combinations of allowed arguments
 */
type AllOverloads = {
  [Prop in MyKeys]:
  Prop extends keyof DataParams
  ? (key: Prop, data: DataParams[Prop]) => any
  : (key: Prop) => any
}

/**
 * Convert all allowed combinations to function overload
 */
type Overloading = UnionToIntersection<Values<AllOverloads>>

const myCoolGeneric: Overloading = <Key extends MyKeys>(
  key: Key,
  data?: Key extends keyof DataParams ? DataParams[Key] : void
) => null as any

myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok

Playground

在 TypeScript 中,您可以像在 JS 中一样使用方括号表示法:DataParams[Key]

使用通用参数更新

type MyKeys = 'lorem' | 'ipsum' | 'dolor';

interface DataParams {
  lorem: { year: number };
  ipsum: { to: string };
}

// credits goes to https://stackoverflow.com/a/50375286
// function intersection producec - functin overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Values<T> = T[keyof T]

/**
 * Generate all possible combinations of allowed arguments
 */
type AllOverloads<Keys extends string, Mappings> = {
  [Prop in Keys]:
  Prop extends keyof Mappings
  ? (key: Prop, data: Mappings[Prop]) => any
  : (key: Prop) => any
}

/**
 * Convert all allowed combinations to function overload
 */
type Overloading<Keys extends string, Mappings> =
  UnionToIntersection<Values<AllOverloads<Keys, Mappings>>>

const myCoolGeneric: Overloading<MyKeys, DataParams> = (
  key: string,
  data?: unknown
) => null as any

myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok

更新 3

type MyKeys = 'lorem' | 'ipsum' | 'dolor';

interface DataParams {
    lorem: { year: number };
    ipsum: { to: string };
}


// credits goes to https://stackoverflow.com/a/50375286
// function intersection producec - functin overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;
type IsNever<T> = [T] extends [UnionToIntersection<T>] ? true : false;

type Values<T> = T[keyof T]

/**
 * Generate all possible combinations of allowed arguments
 */
type AllOverloads<Mappings, Keys extends string> = {
    [Prop in Keys]:
    Prop extends keyof Mappings
    ? (key: Prop, data: Mappings[Prop]) => any
    : (key: Prop) => any
}

/**
 * Convert all allowed combinations to function overload
 */
type Overloading<Mappings, Keys extends string> =
    keyof Mappings extends Keys
    ? UnionToIntersection<Values<AllOverloads<Mappings, Keys>>>
    : never


const myCoolGeneric: Overloading<DataParams, MyKeys> = (
    key: string,
    data?: unknown
) => null as any

myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok

尝试添加到 DataParams 非存在键。 TS 会抛出错误