TypeScript:获取对象内部对象键的类型

4

首先,对于标题不太好,非常抱歉。我确实不知道如何描述这个问题,也许这就是为什么我还没有找到解决方案的原因!

以下是一个小片段,展示了我的问题:

type Type<T> = {
  key: keyof T,
  doStuff: (value: T[typeof key]) => void
//                          ^^^
// TS2034: Cannot find name 'key'.
};

我希望你能够理解我的意图(希望如此)。我已经尝试过几次解决这个问题,但每次参数都变成了所有可用类型的联合。

const Test: Type<{ var1: string, var2: number }> = {
  key: 'var1',
  doStuff: (value) => {}
//          ^^^^^
// (parameter) value: string | number
};

如果有人能帮我解决这个问题,我将非常感激。如果您需要关于我正在尝试做什么或我已经尝试了什么的其他信息,请告诉我!


1
在TypeScript中没有内置的存在类型,因此唯一的方法是使TypeTK extends keyof T中都是泛型。 - jcalz
2个回答

8

在TypeScript中没有内置的存在类型,因此您不能说“我不关心key是哪个键,但它需要是T某个键。”

唯一的方法是使TypeTK extends keyof T中都是泛型,像这样:

type Type<T, K extends keyof T> = {
  key: K,
  doStuff: (value: T[K]) => void
};

然后,您可以像这样指定Test:
const Test: Type<{ var1: string, var2: number }, "var1"> = {
  key: 'var1',
  doStuff: (value) => { } // value is inferred as string
}

这个方法可行,但也许您不喜欢必须在类型和key属性中手动指定"var1"。 不幸的是,您不能只指定T并使K被推断出来,至少目前还不行。 最终应该会有一种方式可以实现部分类型参数推断, 可能会在2018年8月,TypeScript 3.1尽快实现。
目前,您可以通过柯里化的方法进行变通,其中一个泛型函数返回另一个泛型函数,您指定一个并让另一个被推断出来。 就像这样:
const typeFor = <T>() => <K extends keyof T>(type: Type<T, K>) => type;

// T is specified manually
const typeForVar1StringVar2Number = typeFor<{ var1: string, var2: number }>();

// K is inferred from the argument
const Test2 = typeForVar1StringVar2Number({
  key: 'var1',
  doStuff: (value) => { } // value is inferred as string
});

这需要更多的操作,但它可以避免您为 K 写出 'var1'

好的,希望这有所帮助。祝你好运!


编辑:我看到你确实需要存在类型,因为你需要一个这些东西的数组。当你有文字联合(比如T没有字符串索引时的keyof T)时,获取类似存在类型的一种方法是使用distributive conditional types
type PossibleTypes<T> = keyof T extends infer K ? 
  K extends any ? Type<T, K> : never : never;

PossibleTypes<T>将成为每个K(来自T的所有键)对应的Type<T, K>的联合类型。让我们用它来创建该数组:

type ArrayOfPossibleTypes<T> = Array<PossibleTypes<T>>
const asArrayOfPossibleTypes = <T>(arr: ArrayOfPossibleTypes<T>) => arr;
const testArray = asArrayOfPossibleTypes<{ var1: string, var2: number }>([
  {
    key: 'var1', doStuff(value) { /* value is string */ }
  }, {
    key: 'var2', doStuff(value) { /* value is number */ }
  }
]);

推断看起来很不错。如果你不太害怕分布式条件类型,我想这可能适合你。
如果其他方法都失败了,在TypeScript中有一种存在类型的实现,但它涉及到延续传递,可能对您的用例来说过于复杂。以下是一个完整的示例:
type ExistentialType<T> = <R>(f: <K extends keyof T>(x: Type<T, K>) => R) => R;

与其说“我是某个 K 类型的 Type<T, K>”,不如说“如果你给我一个可以操作任何 Type<T, K> 的函数,我可以为你调用该函数并返回结果”。让我们创建一个:

const exType: ExistentialType<{ var1: string, var2: number }> = 
  (f) => f({ key: 'var1', doStuff(value) { } });

然后使用它:
const obj = {var1: "hey", var2: 123};
exType(function (t) { if (t.doStuff) t.doStuff(obj[t.key]) });

这种内外颠倒的方式很古怪有趣,有时也很实用...但在这里可能过于浪费。

好的,希望那有所帮助。再次祝你好运。


谢谢回复!不幸的是,这并没有真正解决我的问题,但那是我的错。我忘了提到我想将具有doStuff函数的类型用作数组(例如https://pastebin.com/gbDrNxSp)。我想现在还不可能实现这个,对吗? - houfio
取决于你所说的“可能”是什么意思。已更新。 - jcalz

0

如果您不介意将密钥放入类型参数中:

export type Type<T, V extends keyof T> = {
    key: V, // not required
    doStuff: (value: T[V]) => void
};

const Test: Type<{ var1: string, var2: number }, "var1"> = {
    key: "var1", // not required
    doStuff: (value): void => {
        // value is type string
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接