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

核心定义

{ [key: string]: unknown } 描述一种对象:索引签名 key :键不固定,但必须是 string 类型;属性值类型固定为 unknown,以接收任何类型的数据。

语法拆解

[key: string] 是索引签名参数。方括号 [] 是必须的语法结构,表示这是一个通配符规则,不是具体属性名。key 只是个占位符,叫 propid 都行。: 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"
};
相关推荐
揽昕2 小时前
判断对象是否含有某个属性
开发语言·前端·javascript
phltxy3 小时前
解锁JavaScript WebAPI:从基础到实战,打造交互式网页
开发语言·javascript
前端小趴菜053 小时前
TypeScript
前端·typescript
getapi3 小时前
在宝塔面板中部署 Vue 项目打包后的 dist 文件作为前端
前端·javascript·vue.js
5967851544 小时前
css浮动
前端·css·html
我想发发发4 小时前
已经安装了ROS环境却还是报错`ModuleNotFoundError: No module named ‘rclpy‘`
前端·人工智能·chrome·机器人
—Qeyser4 小时前
Flutter 组件通信完全指南
前端·javascript·flutter
天天进步20154 小时前
从脚本到服务:5 分钟通过 Botasaurus 将你的爬虫逻辑转化为 Web API
前端·爬虫
沛沛老爹4 小时前
Web转AI架构篇:Agent Skills vs MCP-混合架构设计模式实战指南
java·前端·人工智能·架构·llm·rag