当你对 TS 类型标注的对象调用 Object.keys(obj) 时,返回值类型是 string[] 而不是 (keyof typeof obj)[]。这意味着用返回的 key 去访问 obj[key] 时会报类型错误。这是 TS 有意为之的设计,因为 JS 对象在运行时可能包含额外的属性。
// Object.keys() 返回 string[] 而不是 (keyof T)[]
interface User {
id: number;
name: string;
email: string;
}
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
// 常见错误写法
const keys = Object.keys(user); // 类型:string[]
keys.forEach(key => {
// console.log(user[key]); // 错误!
// 类型 "string" 不能用于索引类型 "User"
});
// 为什么 TS 这样设计?
// JS 对象在运行时可能包含额外的属性
const obj = { id: 1, name: 'Alice' };
// 运行时可能被注入额外属性
// obj.extraProp = 'something';
// Object.keys(obj) 会返回 ['id', 'name', 'extraProp']
// 如果 TS 推断为 ('id' | 'name')[],extraProp 就无法索引
// 解决方案一:类型断言(简单场景)
Object.keys(user).forEach(key => {
console.log(user[key as keyof User]); // OK
});
// 解决方案二:自定义类型安全的 keys 函数
function typedKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
const safeKeys = typedKeys(user); // 类型:("id" | "name" | "email")[]
safeKeys.forEach(key => {
console.log(user[key]); // OK,类型安全
});
// 解决方案三:Object.entries 同样的问题
// Object.entries(user) 返回 [string, string | number][]
function typedEntries<T extends object>(obj: T): [keyof T, T[keyof T]][] {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}
Object.keys 返回 (keyof T)[],在运行时遇到额外属性时会出错。string[] 是宽松但安全的类型,它迫使你用类型断言来确认你知道自己在做什么。keys 函数是推荐做法,在类型安全边界明确断言,使用方无需关心。typedKeys 假设对象没有额外属性,如果你的对象确实有额外属性,运行时仍可能出错,但这是你主动选择了这个假设。Object.entries(obj) 返回 [string, ValueType][],同样需要类型断言。// 常见实际场景:遍历对象构建查询参数
interface QueryParams {
keyword: string;
page: number;
status: 'active' | 'inactive';
}
function buildQuery(params: QueryParams): string {
const parts: string[] = [];
const keys = typedKeys(params); // 类型安全的键数组
keys.forEach(key => {
const value = params[key]; // 类型安全访问
parts.push(`${key}=${encodeURIComponent(String(value))}`);
});
return parts.join('&');
}
// 场景:根据 key 动态读取表单数据
interface FormData {
username: string;
password: string;
remember: boolean;
}
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const form: FormData = { username: 'admin', password: '123456', remember: true };
const username = getField(form, 'username'); // string
const remember = getField(form, 'remember'); // boolean