TypeScript进阶:解锁对象键迭代的隐藏技巧在TypeScript中迭代对象键可能会出现一些令人抓狂的情况。来看一
在TypeScript中迭代对象键可能会出现一些令人抓狂的情况。来看一个例子,应该大多数人都会经历过:
type User = {
name: string;
age: number;
};
function printUser(user: User) {
Object.keys(user).forEach((key) => {
// Doesn't work!
console.log(user[key]);
});
}
除非你知道一些技巧,否则这并不简单。这里是我所知道的所有解决方案。
原因分析
使用Object.keys进行迭代不起作用,因为Object.keys返回的是一个字符串数组,而不是所有键的联合。这是TypeScript有意为之,不能修改的。
常见的解决方法是通过keyof typeof,将key的类型强制转换为对象的key值枚举:
const user = {
name: "Daniel",
age: 26,
};
const keys = Object.keys(user) as Array<keyof typeof user>;
keys.forEach((key) => {
console.log(user[key as keyof typeof user]);
});
又或者通过内联缩小类型来解决。
function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T {
return k in x;
}
const keys = Object.keys(user);
keys.forEach((key) => {
if (isKey(user, key as keyof typeof user)) {
console.log(user[key]);
}
});
这里的问题在于:使用Object.keys没有按你预想的那样返回你需要的类型。
Typescript没有返回包含所有键的类型,而是将其推测为字符串数组。
TypeScript对于对象类型的设计是开放式的。不可否认,在许多情况下,我们不能保证Object.keys返回的键实际上存在于对象上,所以将它们推测为字符串数组是唯一个退一步海阔天空的解决方案。这里有这个问题相关的讨论:github issue
for...in循环,也同样有类似的问题。
解决方案
也许在许多情况下,你是知道对象的key值有哪些的。
那么,你该怎么做呢?
解决方案1:将类型转换为keyof typeof
第一个方案是使用keyof typeof将键转换为更具体的类型。
在下面的示例中,我们将Object.keys的结果转换为包含这些键的数组。
const user = {
name: "Daniel",
age: 26,
};
const keys = Object.keys(user) as Array<keyof typeof user>;
keys.forEach((key) => {
console.log(user[key]);
});
也可以将key强制转换为对应的类型。
const keys = Object.keys(user);
keys.forEach((key) => {
console.log(user[key as keyof typeof user]);
});
使用as在任何形式上通常是不安全的,确实如此,它会对现在或者将来存在一些风险,因为它是人为的指认。
const user = {
name: "Daniel",
age: 26,
};
const nonExistentKey = "id" as keyof typeof user;
const value = user[nonExistentKey];
// 没有错误!
解决方案2:类型谓词
让我们看看一些更智能、更安全的解决方案。如何使用类型谓词?
通过使用isKey,我们可以在索引之前检查键是否真的在对象上。
我们通过在isKey的返回类型中使用is语法,让TypeScript正确推断。
function isKey<T extends object>(x: T, k: PropertyKey): k is keyof T {
return k in x;
}
const keys = Object.keys(user);
keys.forEach((key) => {
if (isKey(user, key)) {
console.log(user[key]);
}
});
解决方案3:泛型函数
让我们看看一个稍微奇怪的解决方案。在泛型函数内,使用in操作符确实会将类型缩小到键。
function printEachKey<T extends object>(obj: T) {
for (const key in obj) {
console.log(obj[key]);
}
}
printEachKey({
name: "Daniel",
age: 26,
});
解决方案4:将Object.keys包装在函数中
另一个解决方案是将Object.keys包装在一个返回转换后类型的函数中。
const objectKeys = <T extends object>(obj: T) => {
return Object.keys(obj) as Array<keyof T>;
};
const keys = objectKeys({
name: "Daniel",
age: 26,
});
console.log(keys);
结论
个人更加推荐方案一,因为它更具可读性,理解上也更直观。
转载自:https://juejin.cn/post/7410645914585579547