gpt4 book ai didi

typescript - 当两种类型都定义了相同的键但值的类型不同时,为什么我不能在另一种类型上使用一种类型的 keyof

转载 作者:行者123 更新时间:2023-12-05 04:41:10 26 4
gpt4 key购买 nike

例如。给定以下代码。我在最后一个 compat[k] 上收到 typescript 错误,错误说

Type 'keyof T' cannot be used to index type 'Partial<CompatType>'

export type KeysOfType<T, U, B = false> = {
[P in keyof T]: B extends true
? T[P] extends U
? U extends T[P]
? P
: never
: never
: T[P] extends U
? P
: never;
}[keyof T];

export type BigIntKeys<T> = KeysOfType<T, bigint, true>

export type CompatType<T> = Omit<T, BigIntKeys<T>> &
{
[Property in BigIntKeys<T>]: string;
};

export function compatModel<T>(model: T): CompatType<T> {
const compat: Partial<CompatType<T>> = {};
for (const k of Object.keys(model) as Array<keyof T>) {
const v = model[k];
compat[k] = typeof v === "bigint" ? v.toString() : v;
}
return compat as CompatType<T>;
};

类型应该在它们的键上完全重叠,但对象上的值的类型不同。这应该意味着我可以使用一个上的键来索引另一个,但它不是以这种方式出现的。我有什么误解或我错了吗?

TS Playground

最佳答案

这是 TypeScript 的设计限制;见microsoft/TypeScript#28884 .根据this comment ,“使用 Pick<T, K> 或通过其他方式构建的高阶类型的互补子集,不可分配回该高阶类型。”

所以像 Omit<T, K> & Record<K, string> 这样的类型其中 K extends keyof T不会被视为具有与 T 相同的键,即使它几乎必须。编译器比较 Exclude<keyof T, K> | Extract<keyof T, K>keyof T但在 T 时不认为它们相等和/或 K未指定 generic类型:

function foo<T, K extends keyof T>(a: keyof T) {
const b: Extract<keyof T, K> | Exclude<keyof T, K> = a; // error!
}

对于任何特定类型TK , 编译器可以完全评估 Extract<keyof T, K> | Exclude<keyof T, K>并看到它与 keyof T 相同,但是当T和/或 K不是特定类型,编译器延迟此评估,因此它不知道它们是否相同。


您可以做的一件事是构建 CompatType作为同态 mapped type直接来自 T ,并使用 conditional type决定特定键是否为 K来自 keyof T将是 BigIntKeys<T> 的一部分并相应地选择值类型:

type CompatType<T> = { [K in keyof T]: 
T[K] extends bigint ? bigint extends T[K] ? string : T[K] : T[K]
}

这会产生更好看的类型,

type Check = CompatType<{ a: string, b: bigint, c: number, d: boolean }>;
/* type Check = {
a: string;
b: string;
c: number;
d: boolean;
} */

并且编译器知道 CompatType<T>肯定与 T 具有相同的键,即使是通用的 T :

export function compatModel<T>(model: T): CompatType<T> {
const compat: Partial<CompatType<T>> = {};
for (const k of Object.keys(model) as Array<keyof T>) {
const v = model[k];

compat[k]; // no error here

compat[k] = typeof v === "bigint" ? v.toString() : v; // still error here, unrelated
}
return compat as CompatType<T>;
};

当然,当您尝试分配 typeof v === "bigint" ? v.toString() : v 时,您仍然会遇到错误。至 compat[k] ,但那是因为编译器并不真正知道如何验证某些东西是否可分配给条件类型(参见 microsoft/TypeScript#33912 ),也不理解 compat[k] 类型之间的相关性。当被写入和 typeof v === "bigint" ? v.toString() : v 的类型时从中读取时(请参阅 microsoft/TypeScript#30581,尤其是根据 microsoft/TypeScript#30769 写入联合需要交集这一事实)。这些问题超出了问题的范围,这只是“为什么 keyof 对我不起作用”。


无论如何,在您确定自己所做的是正确的但编译器不正确的情况下,您可以使用type assertions。或 the any type放宽类型检查以使编译器满意。例如:

export function compatModel<T>(model: T): CompatType<T> {
const compat: Partial<Record<keyof T, any>> = {};
for (const k of Object.keys(model) as Array<keyof T>) {
const v = model[k];
compat[k] = typeof v === "bigint" ? v.toString() : v;
}
return compat as CompatType<T>;
};

这里我们告诉编译器不要担心 compat 的属性值类型,我们只是返回它 as CompatType<T> .只要您绝对 100% 确定输入的内容是正确的,就可以这样做。虽然,您可能不应该那么确定:

const hmm = compatModel({ a: Math.random() < 10 ? 3n : 3 });
hmm.a // number | bigint
if (typeof hmm.a !== "number") {
3n * hmm.a; // no error at compile time, but runtime 💥 "can't convert BigInt to number"
}

a 的类型属性是 number | bigint , 根据 CompatType<{a: number | bigint}> 的任一定义会变成{a: number | bigint}而不是正确的 {a: number | string} .所以编译器认为 hmm.a可能是 bigint即使那是不可能的。这里也有修复,但这些也超出了范围,答案已经比我想要的要长。

这只是一个警告,您在使用类型断言或 any 时应格外小心。克服编译器错误,因为这样的错误更有可能泄漏。

Playground link to code

关于typescript - 当两种类型都定义了相同的键但值的类型不同时,为什么我不能在另一种类型上使用一种类型的 keyof,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70129094/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com