TypeScript细碎知识点:TS中的索引签名
一、概述
在大多数情况下,我们在使用对象前就可以确定对象的结构,并为对象中的属性添加准确的类型。
但是当我们无法确定对象中有哪些属性时,或者说对象中可以出现任意多个属性时,此时,我们就可以用索引签名类型了。
⏰ 索引签名,主要是用来规定索引和属性值的类型,索引类型只能是 string 类型 或者 number 类型 或者 symbol 类型。
🔔 在 TypeScript 中,索引签名类型包括字符串索引签名类型和数字索引签名类型。
二、字符串索引签名类型
1. 语法格式
interface MyObj { [key: string]: number; }
上面的这段代码中,使用 [key: string] 来约束该接口中允许出现的属性名称,其中 key 表示字符串索引的标识符,可以是任何有效的属性名,而 number 表示对应属性的值的类型,可以是任意类型。
2. 用法
interface MyObj { [key: string]: number; } let obj: MyObj = { a: 1, b: 2, c: 3, d: 4 }
如果对象中属性值的类型不是 number 类型,代码会报错。
interface MyObj { [key: string]: number; } let obj: MyObj = { a: 1, // 🙆:正确 b: 2, // 🙆:正确 name: "Echo", // 🙅报错:不能将类型“string”分配给类型“number” bool: true, // 🙅报错:不能将类型“boolean”分配给类型“number” };
3. 特点
- 一旦定义了索引签名,那么必选属性和可选属性的类型都必须是它的类型的子集。
- 返回的属性值的类型必须是索引签名指定的类型。
- 可以与其他明确声明的属性共存。
- 可以同时拥有多种类型的索引签名。
// Ok interface Person { // 同时拥有 string 类型和 number 类型的索引签名 [props: string]: string | number; // 可以定义其它属性,但是属性值的类型必须是索引签名类型的子集 // 这里的属性值类型必须是 string 类型或者 number 类型 name: string; age: number; } // Error interface User1 { [prop: string]: string | number; // 🙅报错:类型“string | undefined”的属性“name”不能赋给“string”索引类型“string | number”。 // ‘name’的属性值类型虽然为’string‘是‘string | number’的子集,但是当其为可选时,值类型就变为‘string | undefined 类型。 name?: string, age: number } // Error interface User { [props: string]: string | number; name: string; age: number; // 🙅报错:类型“true”的属性“bool”不能赋给“string”索引类型“string | number”。 // 由于上面的索引签名规定了只能是 string 类型或者 number 类型,所以这里不能写其它类型 bool: true; }
- 可以访问对象中已存在的属性,如果访问对象中不存在的属性,会返回 undefined。
interface Person { [props: string]: string | number; name: string; age: number } const person: Person = { name: 'Echo', age: 26, city: 'GuangZhou', phone: 13000000000, gender: 'Male', } //访问存在的属性 console.log(person.name); // 输出:Echo console.log(person["age"]); // 输出:26 console.log(person.city); // 输出:GuangZhou console.log(person["phone"]); // 输出:13000000000 console.log(person["gender"]); // 输出:Male //访问不存在的属性 console.log(person["address"]); // 输出:undefined
三、数字索引签名类型
当一个对象具有数字索引签名时,它意味着该对象可以使用数字来索引并获得相应的属性的值。
1. 语法
interface MyArr { [index: number]: string }
上面的这段代码中,我们定义了 MyArr 接口,它具有索引签名。这个索引签名表示了当用 number 去索引 MyArr 时会得到 string 类型的返回值。
2. 用法格式
interface MyArr { [index: number]: string; } const arr: MyArr = ["Echo", "James", "John", "Steven"]; console.log(arr[0]); // Echo console.log(arr[1]); // James console.log(arr[2]); // John console.log(arr[3]); // Steven
3. 特点
- 可以同时拥有多种类型的索引签名。
- 返回的属性值的类型必须是索引签名指定的类型。
interface MyArr { [index: number]: string | number; } const arr: MyArr = [1, 'a', 2, 'b'];
四、注意事项
1、索引签名的键只能是 string、number、symbol 或者模板文本类型
interface MyObj { //🙅报错:索引签名参数类型必须是 “string”、“number”、“symbol”或模板文本类型。 //这里参数类型为boolean类型了 [key: boolean]: boolean; }
2. 不能多次定义相同类型的索引签名
interface MyObj { //🙅报错: 类型“string”的索引签名重复。 [key: string]: string; [key: string]: number; }
五、索引签名类型 VS 泛型工具类型Record<K, T>
先看看下面的代码,我们发现索引签名类型和泛型工具类型 Record<K, T> 很相似:
const object1: Record<string, string> = { name: "Echo" }; // 正确 const object2: { [key: string]: string } = { name: "Echo" }; // 正确
但实际上它们两者之间还是有区别的:
1. 用途不同
-
索引签名类型:主要用于定义具有动态属性的对象,允许通过字符串或数字索引来访问属性。
interface MyObj { [key: string]: string | number } const obj: MyObj = { name: "Echo", age: 26 } console.log(obj.name); // Echo console.log(obj["age"]); // 26
-
Record<K, T>:主要用于生成一个新的类型,用于表示具有固定类型的属性集合,其中所有属性的值都具有相同的类型。
interface Person { name: string; gender: string; } type NewPerson = Record<keyof Person, string>; const person: NewPerson = { name: "Echo", gender: "Male", }
2. 类型约束
-
索引签名类型:对于索引签名,键的类型可以是 string、number、symbol 或者模板文本类型,值的类型可以是任意类型。
interface MyObj { //🙅报错:索引签名参数类型必须是 “string”、“number”、“symbol”或模板文本类型。 //这里参数类型为boolean类型了 [key: boolean]: boolean; }
-
Record<K, T>:对于 Record<K, T>,类型参数 K 的类型可以是 string、number 或 symbol,可以接收字面量类型或者字面量类型组成的联合类型来作为参数,而类型参数 T 可以是任意类型。
比如使用联合类型来作为 Key 值:type Person = Record<"name" | "age" | "gender" | "bool", string | number | boolean>; const person: Person = { name: "Echo", age: 26, gender: "Male", bool: true, }