如果将键写为字符串文字,我可以使用 TypeScript 的
in
关键字以类型安全的方式检查对象是否具有键:
function guardHasTest <Data extends object> (
value: Data
): Data & Record<'test', unknown> {
if (!('test' in value)) {
throw new Error('Missing key')
},
return value
}
但是,如果我将键设为动态字符串,相同的语法不会缩小类型:
function guardHasKey <Data extends object, Key extends string> (
value: Data,
key: Key
): Data & Record<Key, unknown> {
if (!(key in value)) {
throw new Error('Missing key')
}
return value
// Type 'Data' is not assignable to type 'Data & Record<Key, unknown>'.
Type 'object' is not assignable to type 'Data & Record<Key, unknown>'.
Type 'object' is not assignable to type 'Data'.
'object' is assignable to the constraint of type 'Data', but 'Data' could be instantiated with a different subtype of constraint 'object'.
Type 'Data' is not assignable to type 'Record<Key, unknown>'.
Type 'object' is not assignable to type 'Record<Key, unknown>'.ts(2322)
}
我不想使用自定义类型保护,因为它们的逻辑不是类型安全的:
export function isKeyOf<T, const Key extends string>(
obj: T,
key: Key
): obj is T & Record<Key, unknown> {
return true // No error, even though the logic is not correct
}
如何让函数以完全类型安全的方式动态检查对象是否具有键?
为了解决在以类型安全的方式检查对象中是否存在动态键时使用 TypeScript 类型系统所面临的问题,您可以改进您的方法。 TypeScript 的类型系统在动态推断类型时有其局限性,特别是当您需要根据运行时条件缩小类型时。
这是使用类型谓词的改进方法。
您希望您的函数验证对象中是否存在键,并且让 TypeScript 了解此检查会影响对象的类型。我们可以通过类型谓词来实现这一点,它允许您通知 TypeScript 如果函数返回 true,则对象值确实将具有 Key 类型的键。
以下是编写此类函数的方法:
function hasKey<Data extends object, Key extends PropertyKey>(
value: Data,
key: Key
): value is Data & Record<Key, unknown> {
return key in value;
}
function ensureHasKey<Data extends object, Key extends PropertyKey>(
value: Data,
key: Key
): Data & Record<Key, unknown> {
if (!hasKey(value, key)) {
throw new Error(`Missing key: ${key}`);
}
return value;
}
类型保护(hasKey):此函数检查对象中是否存在键。它使用类型谓词 value 是 Data & Record
确保函数(ensureHasKey):该函数使用hasKey来确保key存在于对象中。如果该键不存在,则会抛出错误。如果该键存在,它将返回该对象作为 Data & Record
使用方法如下
const obj = { name: "Alice", age: 25 };
// This will throw an error if 'age' key is missing
const validatedObj = ensureHasKey(obj, 'age');
// Now TypeScript knows 'validatedObj' has a property 'age'
console.log(validatedObj.age); // 25
// This will throw an error because 'salary' does not exist in 'obj'
const anotherObj = ensureHasKey(obj, 'salary');
这种方法确保了编译时和运行时的类型安全。类型谓词允许 TypeScript 的类型系统理解并相信运行时检查(in 运算符)对于根据键的存在缩小对象类型的范围是有效的。