类型表示具有特定类型键的父级

时间:2019-12-13 01:19:44

标签: typescript types

我使用此代码将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
  }
}

1 个答案:

答案 0 :(得分:1)

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


有两种一般的处理方法:一种是尝试找到另一种表达类型约束的方式,以使编译器 可以验证您的代码,另一种方法是使用{ {3}}告诉编译器不要担心,您正在保证所做工作的安全性。在后一种情况下,您应该非常小心,首先要真正进行的工作是安全的。

值得注意的是,您的通用设置员代码是安全的:

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的密钥,可以从中安全地读取类型为U的值,但是您需要安全地将T类型的值写到{em> 的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(尽管类型推断通常可以缓解这种情况)。

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

type assertion