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"
};
相关推荐
神明不懂浪漫1 小时前
【第七章】Java中的常用类
java·开发语言·前端·经验分享·笔记
GuWenyue7 小时前
放弃云端API!一套React+WebGPU本地LLM方案,零数据上传、离线可用
前端·人工智能·前端框架
原则猫8 小时前
函数/变量提升
前端
独泪了无痕9 小时前
Vue3 Hooks使用实战解析
前端·vue.js
shawxlee10 小时前
vue3在public下封装config.js自定义配置动态数据,可在打包后直接修改,方便后端部署及后续维护
前端·javascript·经验分享·vue·团队开发·js·项目优化
用户0595401744611 小时前
把记忆存储的回归测试从手工换成 Playwright + GitHub Actions,线上缺陷降低 80%
前端·css
触底反弹11 小时前
🚀 从 DOM0 级到 React 合成事件:前端事件监听的 20 年演进史
前端·react.js·面试
kyriewen11 小时前
我给前端项目的接口请求套了6层保护——才发现以前一直在裸奔
前端·javascript·面试
颜酱11 小时前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
郝亚军12 小时前
如何安装webstorm、Node.js和vue CLI
前端·javascript·vue.js