likes
comments
collection
share

ts接口高级用法

作者站长头像
站长
· 阅读数 2

索引类型

在TypeScript中,我们可以使用接口(interfaces)来描述对象的结构,包括对象的属性和方法。接口也可以用于描述对象的索引类型和通过索引获取的值的类型

描述索引的类型:

当我们希望一个对象可以通过特定类型的键来访问相应类型的值时,我们可以使用索引类型。通过在接口中定义索引签名,我们可以指定键的类型和对应值的类型。例如:

interface Dictionary {
    [key: string]: number;
}

在这个例子中,我们定义了一个接口Dictionary,它具有字符串类型的键和相应的数字类型的值。

通过索引获取的值的类型:

当我们使用索引访问对象的属性时,TypeScript 会根据索引签名定义的类型来确定获取的值的类型。例如:

const myDictionary: Dictionary = {
    "one": 1,
    "two": 2,
    "three": 3
};

const value: number = myDictionary["two"]; // value的类型为number

在这个例子中,myDictionary对象的键是字符串,值是数字。当我们通过索引获取键为"two"的值时,TypeScript 知道该值的类型为number,因为我们在Dictionary接口中定义了索引签名。

通过接口描述索引的类型和通过索引获取的值的类型,可以使我们在编写代码时更加安全和可靠,因为 TypeScript 会进行类型检查,确保我们使用正确的键和值类型。

对于下述案例

interface RoleDic {
  [id: number]: string;
}
const role1: RoleDic = {
  0: "super_admin",
  1: "admin"
};
const role2: RoleDic = {
  s: "super_admin",  // error 不能将类型"{ s: string; a: string; }"分配给类型"RoleDic"
  a: "admin"
};
const role3: RoleDic = ["super_admin", "admin"];

在这个示例中,我们使用了索引签名(Index Signature)来定义了一个接口RoleDic。索引签名允许我们定义对象或数组的索引的类型和相应的值的类型。

在RoleDic接口中,我们使用了数字索引签名,即[id: number]: string;,这表示该接口允许使用数字作为索引,并且索引对应的值的类型必须是字符串。

然后,我们创建了三个变量role1、role2和role3,它们都是RoleDic接口的实例。

  • role1使用了数字索引,符合RoleDic接口的定义,因此没有问题。
  • role2使用了非数字索引('s'和'a'),它的类型不符合RoleDic接口的定义,所以会产生类型错误。
  • role3是一个数组,它的索引是数字,符合RoleDic接口的定义,因此也是合法的。

这个示例展示了如何使用接口描述索引的类型和相应的值的类型,并且在使用时确保类型的一致性。


需要注意的是,在JavaScript和TypeScript中,对象的属性名可以是字符串或者数值类型当我们使用数值类型作为属性名时,在JavaScript中会将其隐式转换为字符串类型。这种隐式转换可能导致一些意外的行为,特别是当我们有数值类型和字符串类型的同名属性时。

在下面的例子中:

const obj = {
  123: "a", // 数值类型的属性名123
  "123": "b" // 字符串类型的属性名"123"
};
console.log(obj); // { '123': 'b' }

在这个例子中,我们定义了两个属性,一个属性名是数值类型的123,另一个属性名是字符串类型的"123"。由于JavaScript会将数值类型的属性名转换为字符串类型,所以实际上这两个属性名是相同的。当对象字面量被解释时,后面定义的属性会覆盖前面的属性,因此最终对象obj只保留了属性名为"123"的属性。

在TypeScript中,这种行为也是相同的。因此,当我们定义一个接口或类型时,如果使用数值类型作为索引签名,那么当对象中的属性名为数值时,它会被隐式转换为字符串,可能会影响我们的预期结果。 为了避免这种情况,可以明确指定索引类型为字符串,这样不会发生隐式转换,确保对象的属性名始终被视为字符串。

例如

const obj = {
  123: "a", // 这里定义一个数值类型的123这个属性
  "123": "b" // 这里在定义一个字符串类型的123这个属性,这里会报错:标识符“"123"”重复。
};
console.log(obj); // { '123': 'b' }

在这个例子中,我们试图创建一个包含两个属性的对象:

  • 123: "a": 这是一个使用数值类型123作为属性名的属性。
  • "123": "b": 这是一个使用字符串类型"123"作为属性名的属性。

在JavaScript中,对象的属性名会被隐式地转换为字符串类型。因此,当我们在对象字面量中同时使用数值类型和字符串类型的属性名时,实际上它们被当作相同的属性名处理。

但是,在TypeScript中,当我们使用字符串字面量作为属性名时,它会被视为确切的类型。在这个例子中,"123"被视为字符串字面量,而不是数值类型。由于我们同时定义了数值类型123和字符串字面量"123",TypeScript 检测到冲突,并抛出了错误:标识符“"123"”重复。

这是因为TypeScript在设计时考虑到了这种潜在的问题,并严格检查了属性名的类型,避免了在对象字面量中引发混淆的情况。所以,在TypeScript中,这段代码会报错,并且无法编译通过。

继承接口

接口的继承是 TypeScript 中一种非常有用的特性,它允许一个接口(子接口)继承另一个接口(父接口)的成员,从而在子接口中拥有父接口的所有属性和方法,同时还可以在子接口中添加新的属性和方法。

通过继承,子接口可以复用父接口的定义,避免了重复书写相似的结构。这样做不仅提高了代码的可读性和可维护性,还能更好地表示对象之间的关系。

// 父接口
interface Shape {
    color: string;
}

// 子接口继承父接口
interface Circle extends Shape {
    radius: number;
}

// 子接口可以拥有父接口的属性和方法
const circle: Circle = {
    color: "red",
    radius: 10
};

在这个例子中,Circle接口继承了Shape接口,因此Circle接口拥有了color属性。当我们创建一个Circle类型的对象时,它必须包含color和radius属性,符合Circle接口的定义。

接口的继承可以帮助我们构建更加复杂和结构化的类型系统,提高了代码的可靠性和可读性。

在这个示例中,我们使用了接口的继承,其中Tomato和Carrot接口继承了Vegetables接口。接口的继承允许一个接口(子接口)继承另一个接口(父接口)的成员,这样子接口就会包含父接口的所有成员,并且可以额外定义自己的成员。

在这里,Tomato和Carrot接口都继承了Vegetables接口,因此它们都具有color属性,而且还可以定义自己特有的属性。

Tomato接口继承了Vegetables接口,它新增了一个radius属性。一个Tomato对象必须包含color和radius属性。

Carrot接口也继承了Vegetables接口,它新增了一个length属性。一个Carrot对象必须包含color和length属性。

在实际使用中,当你创建一个实例(如tomato和carrot)时,它必须符合接口定义的结构。在tomato的例子中,TypeScript会报错,因为tomato对象缺少了color属性。而在carrot的例子中,它包含了color和length属性,所以是符合Carrot接口定义的。

接口的继承使得代码更具有结构和可读性,能够更好地表示对象之间的关系,并提供了类型安全性,确保了实例的结构与接口定义的要求相符。

interface Vegetables {
  color: string;
}
interface Tomato extends Vegetables {
  radius: number;
}
interface Carrot extends Vegetables {
  length: number;
}
const tomato: Tomato = {
  radius: 1.2 // error  Property 'color' is missing in type '{ radius: number; }'
};
const carrot: Carrot = {
  color: "orange",
  length: 20
};

同时,在 TypeScript 中,一个接口可以继承多个接口,允许你将多个接口的成员合并到一个接口中。这样,新接口就拥有了所有被继承接口的属性和方法。

interface Vegetables {
  color: string;
}
interface Food {
  type: string;
}
interface Tomato extends Food, Vegetables {
  radius: number;
}

const tomato: Tomato = {
  type: "vegetables",
  color: "red",
  radius: 1.2
};  // 在定义tomato变量时将继承过来的color和type属性同时声明

在给定的例子中,有三个接口:Vegetables,Food,和 Tomato。Tomato 接口继承了 Food 和 Vegetables 接口,因此它拥有了这两个接口中定义的所有属性。 Tomato 接口包含了 type(从 Food 继承)和 color(从 Vegetables 继承)属性,以及它自己定义的 radius 属性。

这种继承关系允许你创建更具结构化和层次化的接口,使代码更具可读性和可维护性。在实际开发中,使用接口的继承可以帮助你更好地组织和管理复杂的数据结构和对象关系。

混合类型接口

在TypeScript中,混合类型接口(Hybrid Types)是指一个接口可以描述一个对象具有多种类型的能力,即可以同时具有函数和属性的特征。

意味着一个对象可以被当作函数来调用,同时也可以包含其他属性和方法

一个常见的例子是实现一个计数器,既可以被当作函数来调用,又可以包含一个属性来保存计数状态。在混合类型接口中,我们可以定义一个函数的签名以及属性的结构。

interface Counter {
  (): void;     // 函数签名,表示这个接口可以被调用
  count: number; // 属性,表示这个接口包含一个名为count的属性,类型为number
}

function getCounter(): Counter {
  const c = () => {
    c.count++;
  };
  c.count = 0;
  return c;
}

const counter: Counter = getCounter();
counter(); // 调用函数,递增count属性
console.log(counter.count); // 输出 1
counter(); // 调用函数,再次递增count属性
console.log(counter.count); // 输出 2

在这个例子中,Counter 接口同时描述了一个可以被调用的函数和一个具有 count 属性的对象。getCounter 函数返回了一个满足 Counter 接口定义的对象,该对象既可以被当作函数来调用,也可以访问 count 属性。

转载自:https://juejin.cn/post/7299346713948946469
评论
请登录