首页 > 解决方案 > 涉及泛型对象的泛型属性的赋值无法在泛型函数中正确进行类型检查

问题描述

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

在此示例中,我使用 boolean 作为预期类型。我已经评论了没有按预期进行类型检查的行。您还可以在此处的打字稿游乐场中查看此示例。

我怎样才能表达的签名,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!

TS Playground 上的这个 ^^ 示例。

标签: javascripttypescripttypescript-generics

解决方案


第一个选项(使用KeysOfPropertiesWithType)不起作用,因为打字稿无法推理仍然包含未解析类型参数的条件类型(例如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
}

推荐阅读