在Typescript中,我如何修改一个属性,该属性的类型是我使用泛型键得到的数组的类型?

cld4siwp  于 2023-08-08  发布在  TypeScript
关注(0)|答案(1)|浏览(158)

我正在尝试创建一个泛型函数,它只适用于扩展string[]的对象类型中的属性。例如,对象类型可能是:

type Thing = {
    a: Thing2[],
    b: Thing3[],
    c: number,
}
const thing2 = ['!', '@', '#'] as const;
type Thing2 = typeof thing2[number];
const thing3 = ['1', '2', '3'] as const;
type Thing3 = typeof thing3[number];

字符串
并且该函数将仅接受密钥ab
删除了所有不相关的代码,到目前为止,我已经:

type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];

function append<O extends object, K extends KeysMatching<O, string[]>>(
    obj: O,
    key: K,
    item: O[K] extends (infer U)[] ? U : never
) {
    obj[key].push(item); // Property 'push' does not exist on type 'O[K]'
}


但是TypeScript似乎无法识别obj[key]的类型是string[]。如果可能的话,我也想避免使用as关键字。

k4emjkb1

k4emjkb11#

目前TypeScript中没有原生的KeysMatching<T, V>类型运算符,编译器实际上理解T[KeysMatching<T, V>]可分配给V,用于genericTV。您可以实现您自己的版本,如问题代码所示,但正如您所看到的,它在泛型函数实现中并不像所期望的那样工作。在microsoft/TypeScript#48992上有一个开放的特性请求来支持这类东西,但现在它还不是语言的一部分。
除非这种情况改变,否则我们需要解决它。一种解决方法是更改通用约束的方向,使对象类型O extends Record<K, string[]>(使用Record<K, V>实用程序类型)代替现有约束(其中K extends KeysMatching<O, string[]>),或者在现有约束之外添加O extends Record<K, string[]>。它们从不同的Angular 代表相同或相似的事物。以下是如果你同时做这两件事的情况:

function append<
  O extends Record<K, string[]>, 
  K extends KeysMatching<O, string[]>
>(obj: O, key: K, item: O[K][number]) {
  obj[key].push(item); // okay
}

字符串
现在编译器知道obj[key]可以赋值给string[],因此接受item。我们也可以将item的类型更简单地写成O[K][number],而不是conditional type;现在编译器知道O[K]是数组类型,我们可以直接用number来得到元素类型。
让我们确保它仍然像预期的那样从调用者的Angular 工作:

function foo(thing: Thing) {
  append(thing, "a", "!"); // okay
  append(thing, "b", "2"); // okay
  append(thing, "c", "3"); // error!
  // ---------> ~~~ 
  // Argument of type '"c"' is not assignable to parameter of 
  // type 'KeysMatching<Thing, string[]>'.
  append(thing, "b", "!"); // error
  // --------------> ~~~
  // Argument of type '"!"' is not assignable to parameter of 
  // type '"1" | "2" | "3"'.  
}


看起来不错
Playground代码链接

相关问题