创建一个递归选项类型

时间:2019-01-15 23:53:42

标签: typescript

我正在用Typescript构建一个库,我希望能够接受递归选项散列,其中有许多具有特定类型的预定义键,而其他所有东西都是相同结构的递归版本。我无法弄清楚如何在没有过于通用的类型的情况下通用地执行此操作(索引类型使这一点也可以接受)。

我要做什么的一个例子:

interface FilterOptions {
  age: number
  name: string
}

interface PaginationOptions {
  page: number
  per_page: number
}

type OptionsHash = {
  filters: RecursiveOptions<FilterOptions>
  pagination?: RecursiveOptions<PaginationOptions> 
}

let opts: OptionsHash = {
  filters: {
    age: 10,
    name: "Fred",
    foo: 10, // causes error
    friends: {
      age: 8,
      name: 234, // causes error
      pets: {
        name: 'fido'
      }
    },
    parents: {
      name: "Mom"
    }
  },
  pagination: {
    page: 3,
    friends: {
      page:1,
      per_page: 3
      pets: {
        per_page: 300
      }
    }
  }
}

在上面的示例中,请帮助我定义RecursiveOptions

1 个答案:

答案 0 :(得分:2)

TypeScript没有“除以下内容外的任何键”的自然表示形式。这就需要TypeScript没有的正版subtraction types。因此,无法将RecursiveOptions<FilterOptions>表示为具体类型。

我能看到的最接近给您想要的行为的是创建一个类型函数,该函数验证一个类型R与您想要的RecursiveOptions<T>匹配:

interface FilterOptions {
  age: number
  name: string
}

interface PaginationOptions {
  page: number
  per_page: number
}

type VerifyRecursiveOptions<T, R> =
  R extends object ? { 
    [K in keyof R]: K extends keyof T ? T[K] : VerifyRecursiveOptions<T, R[K]> 
  } : never

type OptionsShape = { filters: any, pagination?: any }

type VerifyOptionsHash<R extends OptionsShape> = {
  filters: VerifyRecursiveOptions<FilterOptions, R['filters']>
  pagination?: VerifyRecursiveOptions<PaginationOptions, R['pagination']>
}

const asOptionsHash = <T extends OptionsShape>(
  x: T & VerifyOptionsHash<T>
): T => x;

您在期望的地方得到错误:

let opts = asOptionsHash({
  filters: {
    age: 10,
    name: "Fred",
    foo: 10, // causes error
    friends: {
      age: 8,
      name: 234, // causes error
      pets: {
        name: 'fido'
      }
    },
    parents: {
      name: "Mom"
    }
  },
  pagination: {
    page: 3,
    friends: {
      page: 1,
      per_page: 3,
      pets: {
        per_page: 300
      }
    }
  }
});

但这很复杂,而且可能很脆弱。您可能想要尝试使用对编译器更友好的类型,而不是随意弯曲TypeScript,例如:

type TractablyRecursiveOptions<T> = T & { 
  recursivePart?: { [k: string]: TractablyRecursiveOptions<T> } 
};

在这种情况下,您只允许T,而不是允许任何额外的密钥添加到recursivePart。然后,这个单一的知名属性可以具有您想要的任何键。但这需要重构您的opts值。

无论如何,希望能有所帮助;祝你好运!