TypeScript 索引签名 vs Record 工具类型

1,058 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

前言

本文将会通过一些实际开发中的问题来介绍一下TypeScript 索引签名 和 Record 工具类型有什么作用,解决了什么样的问题,并且介绍一下它们两的使用方法,以及它们之间的区别在什么地方,什么时候我们应该使用哪一个来解决问题。

解决的问题

在写 JavaScript 代码的时候,有一种我们经常的使用的赋值方式:

let obj = {}
...
obj.name = 'xiaoming'

这种写法在 JavaScript 中并不会出现问题,但是放到 TypeScript 中就会报错,提示这个属性不存在于对象当中。

image.png

比较粗暴的解决方法就是直接用断言或者将 obj 对象的类型改为 any,优雅一点的解决方法就是在 obj 的对象上声明这个属性。

image.png

不过这样的缺点也很明显,就是如果我提前不知道它里面会加上什么属性,我只知道建值的类型是什么,那么该怎么去声明呢,这时候就可以使用索引签名的方法来定义类型。

索引签名

索引签名的思想是在只知道键和值类型的情况下对结构位置的对象进行类型划分

  • 语法:
{ [keyKeyType]: ValueType }

可以应用来解决我们上面的那个问题:

image.png

添加了之后无论你多声明了多少个属性,只要类型满足索引签名定义的类型,就不会报错。

并且你要是有一些已知的建也可以在索引类型下添加声明,结合索引类型来定义该对象:

image.png

索引签名的key类型能够支持 string,number,symbol 或模版字面量类型,而值的类型可以是任意类型

stringnumber 就不必多说了,我们来说一下 symbol 类型 和 模版字面量类型。

symbol 类型

symbol 类型作为 es6 新出现的基本数据类型,通过 Symbol 函数生成,用来表示独一无二的值。我们可以在对象的声明中使用 symbol 类型 来创建绝对不会相同的属性。

在 ES6 中,对象的属性名支持表达式,所以你可以使用一个变量作为属性名:

let symbol = "name";
const aa = {
  [symbol]: "liao"
};
console.log(aa.name); // 'liao'

同样的,我们可以用一个 Symbol 类型来作为传入的变量,就可以生成不会重复的属性:

let symbol = Symbol();
const aa = {
  [symbol]: "liao"
};
console.log(aa[symbol]); // 'liao'

那么在上面的索引签名中的使用也是相同的,我们可以使用 Symbol 来生成独一无二的键名

const s = Symbol();
typeof s; // 'symbol'

let obj:Obj = {
  [s]: 's'
}

console.log(obj[s]); // 's'

更多关于 Symbol 类型的使用有兴趣可以看一下 mdn 上的文档介绍

Symbol - JavaScript | MDN (mozilla.org)

模版字面量类型

模版字面量类型是 TypeScript 4.1 版本引入的新类型,它和 JavaScript 的模板字符串是相同的语法,但是是使用在类型操作当中的。当使用模板字面量类型时,它会替换模板中的变量,返回一个新的字符串字面量:

type World = "world";
 
type hello = `hello ${World}`;

使用模板字面量结合索引签名,我们就能够定义出灵活度更高的类型。

interface PropChangeHandler {
  [key: `${string}Changed`]: () => void;
}

let handlers: PropChangeHandler = {
  idChanged: () => {}, // Ok
  nameChanged: () => {}, // Ok
  ageCha: () => {} // Error
};

Record 工具类型

Record 工具类型 作用和索引签名完全相同,都是在只知道键和值类型的情况下对结构位置的对象进行类型划分

只是使用方式上不太一样,Record 需要传入两个类型,分别代表的就是键和值的类型

image.png

首先我们能够看到这个工具类型的一个具体的定义,第一个参数的key类型必须是 string | number | symbol 类型,然后第二个参数就是值的类型并没有做任何的限制。

image.png

可以看到这个工具类型做完映射之后,其实是和索引签名没有什么区别的。

索引签名 vs Record 工具类型

那么既然 Record 工具类型 映射之后和 索引签名没什么什么区别,那么为什么要存在两种方法去定义索引类型呢。

Record 工具类型能够接收 字面量类型 或者 字面量类型组成的联合类型来作为参数,但是索引签名就不支持,并且 Record 工具类型的表达更见的简单明了,我们能够使用它结合其他的工具类型做出更灵活的类型限制。

比方说我可以使用联合类型来作为建的类型:

type Person = 'name' | 'age' | 'sex'

const person: Record<Person,string> = {
  name:'小明',
  age:'18',
  sex:'男',
}

小贴士补充

问题

上面的索引类型,会限制这个类型中的所有值,比如说:

image.png

这样就必须把索引类型的值改为联合类型

image.png

但是这样的同时,新的不知道键名的属性,就也可以是number属性,这就不符合我们的要求,我们需要只有age这一个number属性,其他属性虽然我们不知道,但是我们知道它们都一定是string类型。

解决

我们可以使用联合类型来解决这个问题:

image.png

这样这个对象中就只能有age这一个number类型,其他类型不为string的话就会报错。

总结

本文介绍了 TypeScript 索引签名 和 Record 工具类型 并且简单的介绍了一下他们之间的区别所在,总而言之内置工具类型被内置了就肯定是由它们使用的地方的,灵活的使用各种工具类型能够更好的实现一些高级用法。

引用

TypeScript的Record类型说明_废材壶的博客-CSDN博客_js record类型