TypeScript { [key: string]: unknown } 索引签名写法和 Record 替代

核心定义

{ [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 是语法糖,专门用来描述"键值对"关系,可读性更好。

使用场景

  1. 字典/哈希表结构 。当你需要一个对象当 Map 用,键是动态字符串(ID、URL、配置名),且值的类型未知或统一时。

    typescript 复制代码
    const cache: Record<string, unknown> = {};
    cache['/api/user'] = { id: 1, name: 'A' };
  2. 处理非结构化数据 。比如解析不知道结构的 JSON,或者处理 FormData 这种键值对。

    typescript 复制代码
    const 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(', ')); // 保证类型安全
    }
  3. 泛型约束 。在泛型里限制一个参数必须是能通过 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"
};
相关推荐
陈随易18 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean21 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn21 小时前
性能监控
前端
Highcharts.js1 天前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn1 天前
构建工具
前端
FRing031 天前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷1 天前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen1 天前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒1 天前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫1 天前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库