如何将泛型类型限制为从值的props中选取键的映射?

时间:2019-12-19 08:33:50

标签: typescript

我想找到一种类型的地图,其中键eq为其值的属性,例如:

type IndexMap<I extends keyof V, V extends { [K in I]: string }> = {
  [P in V[I]]: V
}

const test: IndexMap<'prop', { ok: number, prop: string }> = {
  someVal: {
    ok: 1,
    prop: 'someVal' // Currently, can be violated without compile error.
  }
}

然后,我可以在通用约束中使用它,例如:

class Service<T extends IndexMap<'name', CustomizedPreDefinedApiModel>> {
  // T is limited to IndexMap. Can be used to type check in service methods.
}

任何实现方式?

1 个答案:

答案 0 :(得分:0)

您的类型定义完全可以,但是我们需要了解TS中类型级别和值级别之间的区别。考虑:

type TypeA = IndexMap<'prop', { ok: number, prop: string }>
// is evaluated to:
type TypeA = {
    [x: string]: {
        ok: number;
        prop: string;
    };
}

请注意,我们的键x的类型等于prop键值的类型。因此,一切在类型级别上都是正确的。我们声明prop: string和编译器确实对此进行监视。因此,将数字放在prop中将显示编译错误。

是的,但这不是我们想要的,但是定义的变量类型不会对值进行任何推断。考虑:

const test: IndexMap<'prop', { ok: number, prop: string }> = {
  someVal: {
    ok: 1,
    prop: 'someVal' // Currently, can be violated without compile error.
  }
}

我们静态地定义类型,我们说prop是一个字符串,因此右边的值确实是该类型的有效成员。但是无法通过值更改此类型。推断类型参数的唯一方法是通过函数参数,稍后我将再次介绍。

如果我们使用更严格的类型,结果将有所不同。

type TypeB = IndexMap<'prop', { ok: number, prop: 'val'}>;
// it evaluates into
type TypeB = {
    val: { // correctly narrowed key
        ok: number;
        prop: "val"; // correctly narrowed type
    };
}

因此,当您给它一个prop类型时,原始类型将按预期工作,并创建所需的结构。但是,如何将值级别与类型级别连接起来以免放置此类特定类型,我认为这就是您想要的。

首先对您的类型进行细微更改:

type IndexMap<I extends keyof V, V extends { [K in I]: PropertyKey }> = {
  [P in V[I]]: V
}

我更改了PropertyKey中的字符串,这种类型表示我们需要在给定的密钥I处具有可用作密钥的值(字符串|数字|符号)。

现在如何在不直接指定类型的情况下使值适合给定类型。我们可以使值构造函数。考虑:

const makeTest = <I extends keyof V, V extends { [K in I]: VK }, VK extends PropertyKey>(a:I, b:V) => {
  return {
    [b[a]]: {
      ...b
    }
  } as IndexMap<I, V>
}
const test3 = makeTest('a', { 'a': 'value', b: 1 });
const test4 = makeTest('b', { 'a': 'value', b: 1 }); 

test3test4均为IndexMap类型的正确值。我们不需要显式定义类型,因为我们可以从参数中派生类型,也可以指定类型并适当缩小类型。


总结一下。当说某类给定的类型为a: T时,我们将值a约束为类型T,但我们在值a中输入的内容不会影响类型{{1} }。