涉及泛型对象的泛型属性的分配无法在泛型函数中正确进行类型检查

时间:2019-05-13 02:06:39

标签: javascript typescript typescript-generics

我有一个通用函数,可以读取或写入给定对象的调用者选择的属性。我使用类型约束来确保传递的键用于可分配给相关类型或可从相关类型分配的属性。呼叫代码似乎可以正确检查类型。实现中对象属性的使用未按预期进行类型检查。

在此示例中,我将布尔值用作期望的类型。我已对未进行类型检查的行进行了注释。 You can also see this example in the typescript playground here.

如何表达booleanAssignmentTest的签名,以便类型检查器理解obj[key]的类型为boolean?是否可以通过保持boolean本身通用的方式来完成,以允许统一定义与其他类型一起使用的多个此类相似功能?

type KeysOfPropertiesWithType<T, U> = {
  // We check extends in both directions to ensure assignment could be in either direction.
  [K in keyof T]: T[K] extends U ? (U extends T[K] ? K : never) : never;
}[keyof T];

type PickPropertiesWithType<T, U> = Pick<T, KeysOfPropertiesWithType<T, U>>;

function booleanAssignmentTest<T extends PickPropertiesWithType<T, boolean>, K extends KeysOfPropertiesWithType<T, boolean>>(obj: T, key: K): void {
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // No error, but there should be!
    obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."
}

let foo = { aBool: false, aNumber: 33, anotherBool: false };
booleanAssignmentTest(foo, "aBool"); // Fine!
booleanAssignmentTest(foo, "anotherBool"); // Fine!
booleanAssignmentTest(foo, "aNumber"); // Error: working as intended!

我正在使用tsc版本3.4.5,以备不时之需。

更新

在类似的问题上,我找到了以下答案:https://stackoverflow.com/a/52047487/740958

我尝试应用更简单,效果更好的方法,但是obj[key] = true;语句仍然存在相同的问题。

function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void {
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."
}

let foo = { aBool: false, aNumber: 33, anotherBool: false };

booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!

This ^^ example on TS Playground.

1 个答案:

答案 0 :(得分:4)

第一个选项(使用KeysOfPropertiesWithType)不起作用,因为typescript无法推断仍包含未解析类型参数(例如本示例中的TK)的条件类型

第二个选项不起作用,因为T extends Record<K, boolean>意味着T可以是{ a: false },这意味着分配obj[key] = true无效。通常,T[K]必须扩展类型这一事实并不意味着我们可以在泛型函数中为其分配任何值,约束条件只是告诉我们该值的最低要求是什么,我们尚不知道完整合同T[K]要求。

至少对您的示例代码有效的解决方案是完全不使用T。在这种情况下,似乎没有必要:

function booleanAssignmentTest2<K extends PropertyKey>(obj: Record<K, boolean>, key: K): void {
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true; // Ok now we know T[K] is boolean
}

let foo = { aBool: false, aNumber: 33, anotherBool: false };

booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!

如果您的示例更加复杂,请提供完整的示例,尽管通常情况下,如果您确定可以将值分配给T[K],则解决方案将使用类型断言,因此这是可行的解决方案:

function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void {
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true as T[K]; // ok now
}