TypeScript:声明可索引类型

在 TypeScript中,可索引类型是指那些可以通过索引访问其属性值的类,通常情况下,这些类型被定义为对象或数组,用来模拟数组或字典的行为。可索引类型允许你定义一个接口来指定对象可以被哪些类型的键所索引,这个定义的接口有一个索引签名,用于描述可以使用的索引类型及其对应的返回值类型。

1、基本索引类型语法
你可以通过索引签名(index signatures)来定义可索引类型。索引签名使用 []来表示,并且需要指定键的类型和值的类型。常见的键类型包括 stringnumber。注:现在只支持字符串数字.

  • 定义键值为数字的索引类型
interface StringArray {
  [index: number]: string;
}

例子中StringArray是一个具有数字索引的类型,并且每个索引对应的值都是字符串类型,这意味着任何数字都可以作为索引,并且对应的值必须是数字。

let _myArray: StringArray = ["Bob", "Fred"]
let _myStr: string = _myArray[0];
console.log(_myStr); //Bob
//或
_myStr = _myArray["0"]
console.log(_myStr); //Bob

为什么_myArray[0]和_myArray["0"]都可以呢,后面的字符串索引与数字索引的关系中会说明

  • 定义键值为字符串的索引类型
  1. 基础字符串索引类型的示例
interface NumberDictionary {
  [key: string]: number;
}

例子中NumberDictionary是一个具有字符串索引的类型,并且每个索引对应的值都是数字类型。具体表示NumberDictionary的属性名称为字符串类型,对应的属性值为数字类型。

let _myDict: NumberDictionary = {"a":10, "b":20}
let _myValue:number = _myDict["a"]
console.log(_myValue); // 10
  1. 再实现个索引值类型为任意值的示例
interface StringDictionary {
    [key: string]: any; // 任何字符串都可以作为键,值的类型是任意类型
}

在这个例子中,StringDictionary 接口定义了一个字符串索引签名,这意味着任何字符串都可以作为键,并且对应的值可以是任意类型。

let _anyDict: StringDictionary = {name: "Alice", age: 25, location: "Wonderland" }
console.log(_anyDict["name"]); //输出:Alice
console.log(_anyDict.name); //输出:Alice
console.log(_anyDict["age"]); //输出:25
console.log(_anyDict.age); //输出:25
console.log(_anyDict["location"]); //输出:Wonderland
console.log(_anyDict.location); //输出:Wonderland

注:我们发现["name"].name的取值方式都可获得正确的索引值,表明字符串索引类型声明了obj.propertyobj["property"]两种形式都可以获取索引值

2、字符串索引与数字索引的关系
声明索引类型时可以同时定义字符串和数字索引签名,但 TypeScript 要求数字索引的返回类型必须是字符串索引返回类型的子类型,这是因为在JavaScript中,数字索引会首先被转换为字符串键。

例如:定义了一个数字索引类型,用100(一个number)去获取索引值,这时等同于使用"100"(一个string)去索引。

interface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
}

interface AnimalDictionary {
    [index: number]: Dog;
    [key: string]: Animal
}

在这里,数字索引number返回的是Dog类型,而字符串索引string返回的是Animal类型。因为DogAnimal的子类型,所以这是合法的

// 定义三个动物
let _dog_1: Dog = {
    name: "小白",
    breed: "京巴"
}
let _dog_2: Dog = {
    name: "小黑",
    breed: "柴犬"
}
let _animal_1: Animal = {
    name: "花花"
}
  • 第一种使用方式
let _animalInfo = {
    animal1: _dog_1, 
    100:_dog_1, 
    animal2: _dog_2, 
    200:_dog_2,
    animal3:_animal_1,
    300:_animal_1
}

console.log(_animalInfo["animal1"]); //输出:{ name: '小白', breed: '京巴' }
console.log(_animalInfo[100]); //输出:{ name: '小白', breed: '京巴' }
console.log(_animalInfo["animal3"]); //输出:{ name: '花花' }
console.log(_animalInfo[300]); //输出:{ name: '花花' }
  • 第二种使用方式
let _animalArr = [_dog_1, _dog_2, _animal_1]
console.log(_animalArr[0]); //输出:{ name: '小白', breed: '京巴' }
console.log(_animalArr[1]); //输出:{ name: '小黑', breed: '柴犬' }
console.log(_animalArr[2]); //输出:{ name: '花花' }

3.索引签名与属性的关系
当在一个接口中同时定义了索引签名和具体属性时,所有具体属性的类型必须符合索引签名的约束。

  • 索引签名与具体属性
    当一个接口既有索引签名,又有具体的属性时,索引签名的类型会影响具体属性的类型,反之亦然。具体来说,所有具体属性的类型必须兼容索引签名的返回类型。

错误声明方式:

interface MyDictionary {
    [key: string]: number;
    length: number; // 合法,属性类型与索引签名一致
    name: string // 非法,属性类型与索引签名类型冲突,`name`的类型与索引类型返回值的类型不匹配
    // 类型“string”的属性“name”不能赋给“string”索引类型“number”。
}

在上面的例子中,length属性是合法的,因为它的类型number与索引签名[key: string]: number类型一致。但是name: string属性则会报错,因为string类型与索引签名返回的number类型不兼容。

正确声明方式:

interface MyDictionary {
    [key: string]: any;
    //或
    //[key: string]: number | string;
    length: number; 
    name: string; 
}
let _myInfo: MyDictionary = {length: 180, name: "bob", city: "北京", age: 24}
console.log(_myInfo["length"]); //输出:180
console.log(_myInfo["name"]); //输出:bob
console.log(_myInfo["city"]); //输出:北京
console.log(_myInfo["age"]); //输出:24
  • 具体属性可以细化类型
    尽管索引签名限制了属性的类型,但具体的属性可以是索引签名的类型的子类型。因此,具体属性可以比索引签名的类型更加精确。
interface MyDictionary {
  [key: string]: number;
  specialProperty: 42;  // 具体属性是索引签名类型的子类型
}

在这个例子中,specialProperty 的类型是字面量类型 42,它是 number 类型的一个子类型,因此这种定义是合法的。

  • 混合使用索引签名和属性
    在一些场景下,具体属性和索引签名可以混合使用,但需要谨慎处理类型的一致性。如果你希望某些属性具有不同的类型,可以通过类型联合来实现
interface MyDictionary {
  [key: string]: number | string;
  id: number;
  name: string;
}

在这个例子中,[key: string]: number | string允许所有属性的值既可以是 number 也可以是 string,因此 idname 属性都合法。

4.索结合使用多种索引签名
可以在一个接口中定义多个索引签名,只要它们的键类型不同:

interface MixedDictionary {
    [key: string]: string | number;
    [index: number]: string;
}
  
let mixed: MixedDictionary = {
    0: 'zero',
    one: 1,
    two: 'two'
};

console.log(mixed[0]);
console.log(mixed["one"]);

在这个例子中,MixedDictionary 接口既支持字符串索引也支持数字索引,并且值可以是 stringnumber

5.索只读索引签名
如果你希望索引签名是只读的,可以使用 readonly 关键字,防止了给索引赋值:

interface ReadonlyStringDictionary {
  readonly [key: string]: string;
}

let roObj: ReadonlyStringDictionary = {
  prop: "value"
};

// roObj.prop = "newValue"; // 错误:不能修改只读属性
interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error:你不能设置myArray[2],因为索引签名是只读的。

总结

  • 数字索引类型:[index: number]: Type
  • 字符串索引类型:[key: string]: Type
  • 数字索引类型的返回值必须是字符串索引类型的子类型。
  • 索引签名定义了对象中属性的类型规则,并且所有属性都必须与索引签名类型兼容。

这些特性使 TypeScript 能够强类型地描述对象、数组等数据结构。通过使用可索引类型,你可以更灵活地定义和操作那些具有动态结构的数据集合。这对于处理数据存储、配置对象等场景非常有用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容