核心定义
{ [key: string]: unknown } 描述一种对象:索引签名 key :键不固定,但必须是 string 类型;属性值类型固定为 unknown,以接收任何类型的数据。
语法拆解
[key: string] 是索引签名参数。方括号 [] 是必须的语法结构,表示这是一个通配符规则,不是具体属性名。key 只是个占位符,叫 prop、id 都行。: string 规定了所有属性名必须是字符串。
: unknown 是索引类型的返回值。它表示所有符合 [key: string] 规则的属性,其值都是 unknown 类型。unknown 是类型安全的顶层类型,任何值都能赋给它,但它不能直接赋值给别人,必须先做类型收窄才能用。
为什么要用 unknown 而不是 any?
写成 { [key: string]: any },TypeScript 会彻底关闭类型检查,你乱写 obj.foo.bar 也不报错,容易出现运行时错误。
写成 { [key: string]: unknown },你访问 obj.foo 得到的是 unknown。你必须显式判断类型(如 if (typeof obj.foo === 'string'))或断言,才能操作。这强制你在写代码时就处理风险。
举例:
typescript
const data: { [key: string]: unknown } = {
name: "Alice",
age: 25
};
// TypeScript: 🛑 "不行!这东西是 unknown,不能直接用"
// data.age.toUpperCase();
// 先检查再用:
if (typeof data.age === 'string') {
data.age.toUpperCase();
}
等价写法:Record
Record<string, unknown> 和 { [key: string]: unknown } 在类型系统里完全等价。Record 是语法糖,专门用来描述"键值对"关系,可读性更好。
使用场景
-
字典/哈希表结构 。当你需要一个对象当 Map 用,键是动态字符串(ID、URL、配置名),且值的类型未知或统一时。
typescriptconst cache: Record<string, unknown> = {}; cache['/api/user'] = { id: 1, name: 'A' }; -
处理非结构化数据 。比如解析不知道结构的 JSON,或者处理
FormData这种键值对。typescriptconst jsonString = '{"id": 101, "meta": {"author": "admin"}, "tags": ["ts", "js"]}'; // 1. 先用 unknown 接住,最安全 const parsed: unknown = JSON.parse(jsonString); // 2. 断言成 Record,告诉 TS:作为字典使用 const data = parsed as Record<string, unknown>; // 3. 取出来的值还是 unknown,老老实实做判断 const tags = data['tags']; if (Array.isArray(tags)) { console.log(tags.join(', ')); // 保证类型安全 } -
泛型约束 。在泛型里限制一个参数必须是能通过 string 键索引的对象。
写工具函数时,限制参数必须是个"能按字符串索引的对象",入参可能是从某个 API 返回的,通过该工具函数实现取值类型安全(不确定入参中到底有啥,值的类型也不确定)。typescript// 使用 extends 约束: T 必须是个键值对对象 function getProperty<T extends Record<string, unknown>>(obj: T, key: string) { return obj[key]; // 返回值自动是 unknown } const user = { name: "Bob", age: 30 }; const val = getProperty(user, "name"); // val 类型是 unknown // 拿到手还是得自己收窄类型 if (typeof val === 'string') { console.log(val.toUpperCase()); // ✅ }
关键限制(隐式规则)
这是最容易死的地方:一旦写了索引签名,对象里所有明确写出来的属性,类型必须兼容索引签名的返回值。
例如:
typescript
interface UserNames {
[key: string]: string,
count: number;
// Property 'count' of type 'number' is not assignable to 'string' index type 'string'.
}
修正方法 :如果对象有固定属性且类型不一致,必须把索引签名的值类型改成联合类型(如 string | number | boolean),或者用交叉类型(&)。别让固定属性的类型跟索引签名打架。
使用联合类型:
typescript
interface UserNames {
[key: string]: string | number;
count: number;
}
使用交叉类型:
typescript
type Base = Record<string, unknown>;
// 给通用对象 Base 添加约束,必须要有 id 和 name:
type User = Base & { id: number; name: string };
const u: User = {
id: 1,
name: "Alice",
anyOtherKey: "anything"
};