← 返回导航

Object.keys() 的类型问题

踩坑记录

场景说明

当你对 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]][];
}

关键点解析

使用示例与类型推导


      
// 常见实际场景:遍历对象构建查询参数

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
在 Playground 中尝试