表示父项具有某种类型的键的类型

问题描述 投票:0回答:1

我使用此代码将ChildClass类型的属性动态分配给ParentClass

export type KeyOfValueType<T, U> = {
  [V in keyof T]: T[V] extends U ? V : never
}[keyof T];

class ChildClass {
  public name: string;
}

class ParentClass {
  public child: ChildClass;
}

class Setter {
  public propName: KeyOfValueType<ParentClass, ChildClass>;

  public setProp(parent: ParentClass, child: ChildClass) {
    parent[this.propName] = child; // <== No Error
  }
}

如果尝试创建“通用”设置器,则会出现错误:

class GenericSetter<T, U> {
  public propName: KeyOfValueType<T, U>; // No Error
  public setProp(parent: T, child: U) {
    parent[this.propName] = child; // <== Error U is not assignable to T[{[V in keyof T]: T[V] extends U ? V : never}]
  }
}

class Setter extends GenericSetter<ParentClass, ChildClass> {
  public setProp(parent: ParentClass, child: ChildClass) {
    parent[this.propName] = child; // <== No Error with Parent propName
  }
}
typescript types
1个回答
0
投票

从根本上来说是design limitation, see microsoft/TypeScript#30728。对于依赖于泛型类型参数的条件类型,编译器不会做太多可分配性分析。也许可以对此进行改进,但是会在类型检查器复杂性和编译时间方面付出一些代价,并且只有某些专门从事此类工作的用户才能看到任何好处。因此,我不会指望它会发生。


有两种一般的处理方法:一种是试图找到另一种表达类型约束的方式,以使编译器验证代码,而另一种则是使用type assertion来告知编译器不用担心,您正在为所做工作的安全性担保。在后一种情况下,您应该非常小心,首先要真正进行的工作是安全的。值得注意的是,您的通用设置员代码是

not

安全的:const oops = new GenericSetter<{ a: string }, string | number>() oops.propName = "a"; const myParent = { a: "bar" }; oops.setProp(myParent, 123); console.log(myParent.a.toUpperCase()); // no compiler error, but at runtime: // TypeError: myParent.a.toUpperCase is not a function

您的KeyOfValueType<T, U>方向错误:它为您提供了T的键,从中可以安全地

read

类型为U的值,但是您需要T的键write类型为U的值是安全的。因此,您需要类似:type KeyOfValueTypeForWriting<T, V> = { [K in keyof T]: [V] extends [T[K]] ? K : never }[keyof T]; class FixedGenericSetter<T, U> { public propName!: KeyOfValueTypeForWriting<T, U>; public setProp(parent: T, child: U) { parent[this.propName] = child; // same error as before } } const okay = new FixedGenericSetter<{ a: string }, string | number>() okay.propName = "a"; // error here now
[至少现在您只能将propName设置为可安全写入的键,并且okay.propName = "a"是错误的,因为您无法安全地将string | number写入a{a: string}属性。 

仍然,setProp()内部的可分配性错误仍然存​​在,所以让我们尝试用我前面提到的两种方法来修复它。最简单,破坏最少的修复是类型声明:

class FixedGenericSetterAsserted<T, U> { public propName!: KeyOfValueTypeForWriting<T, U>; public setProp(parent: T, child: U) { parent[this.propName] = child as any as T[KeyOfValueTypeForWriting<T, U>]; // assert } }

这可以快速清除错误,但要对您负责安全。或者,您可以重构类型,以便没有未解决的条件类型,例如:

class FixedGenericSetterRefactored<K extends PropertyKey, U> { public propName!: K; public setProp(parent: Record<K, U>, child: U) { parent[this.propName] = child; } } const fine = new FixedGenericSetterRefactored<"a", string>() fine.propName = "a"; fine.setProp({ a: "okay" }, "works");

这也可以正常工作,但是您的T通用类型参数已消失,并由K的相关键T代替。这样做比较安全,但是您可能还有其他用例,为什么需要指定T而不是K(尽管类型推断通常可以缓解这种情况)。

两种方法都可以为您工作。好的,希望能有所帮助;祝你好运!

Link to code

© www.soinside.com 2019 - 2024. All rights reserved.