程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • TypeScript

    • TypeScript - 介绍
    • TypeScript - 安装和使用
    • TypeScript - 基本类型
    • TypeScript - 编译和配置
    • TypeScript - 文件打包
    • TypeScript - 接口
      • 1. 接口(Interface)
        • 接口的作用
        • 接口的定义
      • 2. 接口初探
        • 使用对象字面量进行类型检查
        • 使用接口进行类型定义
      • 3. 可选属性
        • 可选属性的定义
        • 可选属性的优点
        • 使用场景
      • 4. 只读属性
        • 只读属性的定义
        • ReadonlyArray 类型
        • readonly vs const
      • 5. 额外的属性检查
        • 问题示例
        • 额外属性检查
        • 解决方法
        • 1. 类型断言
        • 2. 索引签名
        • 3. 变量赋值
        • 最佳实践
      • 6. 函数类型
        • 函数类型的定义
        • 函数类型的使用
        • 参数类型推断
      • 7. 可索引的类型
        • 数字索引签名
        • 字符串索引签名
        • 字符串索引的应用
        • 只读索引签名
      • 8. 类的类型
        • 实现接口
        • 接口方法
        • 类静态部分与实例部分的区别
      • 9. 继承接口
        • 单接口继承
        • 多接口继承
      • 10. 混合类型
        • 定义混合类型接口
      • 11. 接口继承类
        • 接口继承类的示例
        • 关键点
    • TypeScript - 函数
    • TypeScript - 类
    • TypeScript - 泛型
    • TypeScript 的导入导出
    • TypeScript - 类型推断
    • TypeScript - 高级类型
  • JS 超集语言 - TypeScript
  • TypeScript
scholar
2023-09-08
目录

TypeScript - 接口

  • 1. 接口(Interface)
    • 接口的作用
    • 接口的定义
  • 2. 接口初探
    • 使用对象字面量进行类型检查
    • 使用接口进行类型定义
  • 3. 可选属性
    • 可选属性的定义
    • 可选属性的优点
    • 使用场景
  • 4. 只读属性
    • 只读属性的定义
    • ReadonlyArray 类型
    • readonly vs const
  • 5. 额外的属性检查
    • 问题示例
    • 额外属性检查
    • 解决方法
    • 最佳实践
  • 6. 函数类型
    • 函数类型的定义
    • 函数类型的使用
    • 参数类型推断
  • 7. 可索引的类型
    • 数字索引签名
    • 字符串索引签名
    • 字符串索引的应用
    • 只读索引签名
  • 8. 类的类型
    • 实现接口
    • 接口方法
    • 类静态部分与实例部分的区别
  • 9. 继承接口
    • 单接口继承
    • 多接口继承
  • 10. 混合类型
    • 定义混合类型接口
  • 11. 接口继承类
    • 接口继承类的示例
    • 关键点

# 1. 接口(Interface)

TypeScript 的一个核心原则是通过检查值的结构来确定类型,这种类型检查被称为「鸭式辨型法」或「结构性子类型化」。接口在 TypeScript 中用于为这些结构命名,并为你的代码或第三方代码定义契约。

# 接口的作用

  • 定义结构:接口用于定义对象的结构,确保对象符合特定的类型。
  • 抽象方法:接口中的所有方法和属性都是没有实现的,即接口中的所有方法都是抽象方法。
  • 实现接口:类可以实现接口,必须提供接口中定义的所有属性和方法。
  • 限制对象:接口可以限制对象的形状,使对象必须包含接口中定义的所有属性。

# 接口的定义

接口的定义类似于一个模板,描述了对象应当具有的属性和方法。

示例:

// 定义一个接口 Demo
interface Demo {
    name: string;   // 必须有一个 name 属性,类型为 string
    age: number;    // 必须有一个 age 属性,类型为 number
    checked: boolean; // 必须有一个 checked 属性,类型为 boolean
}

// 定义一个符合接口结构的对象
let demo: Demo = {
    name: "kele",   // name 属性为 string 类型
    age: 18,        // age 属性为 number 类型
    checked: true   // checked 属性为 boolean 类型
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 接口命名规范:接口名称通常以大写字母开头,属性和方法之间可以用空格、逗号或分号分隔。
  • 类型检查:如果属性的值不符合接口的类型要求(例如 checked 属性不是 boolean 类型),编译器将报错。

# 2. 接口初探

通过一个简单的示例来观察接口是如何工作的:

# 使用对象字面量进行类型检查

在 TypeScript 中,可以直接在函数参数中定义对象的类型结构,这种方式适合简单的类型定义。

示例:

// 定义一个函数,要求参数为具有 label 属性的对象
function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
}

// 定义一个符合类型要求的对象
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);  // 输出:Size 10 Object
1
2
3
4
5
6
7
8
  • 类型检查器:TypeScript 类型检查器会检查 printLabel 函数调用的参数对象是否具有 label 属性,并且类型为 string。
  • 多余属性:传入的对象可以包含比类型要求更多的属性,编译器只检查那些必需的属性是否存在,以及它们的类型是否匹配。

# 使用接口进行类型定义

可以将对象的结构定义为接口,以提高代码的可读性和可维护性。

示例:

// 定义一个接口,描述具有 label 属性的对象
interface LabelledValue {
    label: string;  // label 属性必须是 string 类型
}

// 使用接口作为函数参数的类型
function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}

// 定义一个符合接口要求的对象
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);  // 输出:Size 10 Object
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 接口 LabelledValue:用于描述必须包含一个 label 属性且类型为 string 的对象。
  • 结构匹配:在 TypeScript 中,接口检查的是对象的结构而不是对象是否显式实现了接口,只要对象具有接口定义的所有属性即可通过检查。

注意事项

  • 属性顺序:类型检查器不会检查属性的顺序,只要属性名称和类型正确即可。
  • 接口兼容性:接口的检查基于值的外形(结构),只要符合结构定义即可被接受。

接口是 TypeScript 强大的类型系统的一部分,它允许开发者定义更明确的类型约束,提高代码的可靠性和可维护性。通过接口,开发者可以在编写和维护大型项目时减少错误,并确保模块之间的良好协作。

# 3. 可选属性

在 TypeScript 中,接口中的属性不一定都是必需的。对于某些情况,一些属性可能仅在特定条件下存在,或者在某些对象中可能根本不存在。为此,TypeScript 提供了可选属性,可以使用 ? 符号标记。

# 可选属性的定义

在接口中定义可选属性时,只需在属性名后添加一个 ? 符号。

示例:

// 定义一个接口 SquareConfig,其中 color 和 width 为可选属性
interface SquareConfig {
    color?: string;  // color 属性是可选的
    width?: number;  // width 属性是可选的
}

// 定义一个接口 Square,其中 color 和 area 为必需属性
interface Square {
    color: string;  // color 属性是必需的
    area: number;   // area 属性是必需的
}

// 定义一个函数 createSquare,接受一个 SquareConfig 类型的参数,并返回一个 Square 类型的对象
function createSquare(config: SquareConfig): Square {
    let newSquare = { color: 'white', area: 100 };
    if (config.color) {  // 检查 color 属性是否存在
        newSquare.color = config.color;  // 如果存在,则使用配置的 color
    }
    if (config.width) {  // 检查 width 属性是否存在
        newSquare.area = config.width * config.width;  // 如果存在,则计算 area
    }
    return newSquare;
}

// 使用 createSquare 函数创建一个 Square 对象
let mySquare = createSquare({ color: 'black' });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 可选属性的优点

  1. 灵活性:可选属性允许你在对象中定义一些非必需的属性,使得对象初始化更加灵活。
  2. 错误捕获:当试图访问不存在的属性时,TypeScript 会提供错误提示,帮助开发者发现拼写错误或逻辑错误。

示例:

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): Square {
    let newSquare = { color: 'white', area: 100 };
    if (config.clor) {  // 错误:属性 'clor' 不存在于类型 'SquareConfig' 中
        newSquare.color = config.clor;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 使用场景

  • 当一个对象的属性是可选时,可以使用可选属性来修饰这些属性。
  • 没有 ? 修饰的属性必须在对象初始化时赋值,而可选属性则不需要。

# 4. 只读属性

在 TypeScript 中,有些对象属性只允许在对象创建时赋值,之后不能再被修改。这种属性可以使用 readonly 修饰。

# 只读属性的定义

在属性名前加上 readonly 关键字即可将其标记为只读属性。

示例:

// 定义一个接口 Point,其中 x 和 y 是只读属性
interface Point {
    readonly x: number;  // x 属性是只读的
    readonly y: number;  // y 属性是只读的
}

// 创建一个 Point 对象
let p1: Point = {
    x: 10,
    y: 20
};

// p1.x = 5;  // 错误:无法分配到 "x" ,因为它是只读属性
1
2
3
4
5
6
7
8
9
10
11
12
13

# ReadonlyArray 类型

TypeScript 提供了 ReadonlyArray<T> 类型,与普通数组类似,但所有可变方法都被移除,以确保数组在创建后不可修改。

示例:

let a: number[] = [1, 2, 3, 4];  // 普通数组
let ro: ReadonlyArray<number> = a;  // 只读数组

// ro[0] = 12;    // 错误:索引签名只允许读取
// ro.push(5);    // 错误:'push' 方法在 'readonly number[]' 类型上不存在
// ro.length = 100; // 错误:无法分配到 "length" ,因为它是只读属性
// a = ro;        // 错误:类型 "readonly number[]" 不可分配给类型 "number[]"
1
2
3
4
5
6
7
  • 尝试修改 ReadonlyArray 的元素或调用修改方法会导致错误。
  • 可以通过类型断言将 ReadonlyArray 转换为普通数组以解除只读限制:
a = ro as number[];  // 类型断言解除只读限制
1

# readonly vs const

  • readonly:用于对象属性,表示属性只可在对象初始化时赋值。
  • const:用于变量,表示变量的引用不可更改。

选择方法:

  • 若要将某个变量声明为不可重新赋值,使用 const。
  • 若要将对象的某个属性声明为不可修改,使用 readonly。

通过使用可选属性和只读属性,TypeScript 提供了更灵活的类型系统和更严格的代码检查,有助于提高代码的可靠性和可维护性。

# 5. 额外的属性检查

在 TypeScript 中,接口可以用于定义对象的结构。使用接口时,TypeScript 会检查传入的对象是否符合接口的要求。当你将对象字面量直接传递给函数或赋值给变量时,TypeScript 会进行额外的属性检查,以确保对象不会包含不期望的属性。这种检查有助于捕获潜在的错误,例如属性名拼写错误。

# 问题示例

考虑以下例子,其中定义了一个 SquareConfig 接口,并实现了一个 createSquare 函数:

interface SquareConfig {
    color?: string;  // 可选属性 color
    width?: number;  // 可选属性 width
}

// 定义 createSquare 函数,返回一个具有 color 和 area 属性的对象
function createSquare(config: SquareConfig): { color: string; area: number } {
    let newSquare = {
        color: 'white',  // 默认 color 值
        area: 100        // 默认 area 值
    }
    if (config.color) {
        newSquare.color = config.color;  // 如果提供了 color,则覆盖默认值
    }
    if (config.width) {
        newSquare.area = config.width * config.width;  // 计算 area 值
    }
    return newSquare;
}

// 传入的对象字面量包含拼写错误的 colour 属性
let mySquare = createSquare({ colour: 'red', width: 100 });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 额外属性检查

在上面的代码中,传入 createSquare 的参数对象中有一个 colour 属性,该属性在 SquareConfig 中并未定义。这会导致 TypeScript 提示错误,因为对象字面量会经过额外的属性检查:

// 错误:'colour' 属性不存在于类型 'SquareConfig' 中
let mySquare = createSquare({ colour: 'red', width: 100 });
1
2

# 解决方法

# 1. 类型断言

可以通过类型断言来绕过额外属性检查:

let mySquare = createSquare({ colour: 'red', width: 100 } as SquareConfig);
1

然而,类型断言是一种欺骗编译器的方法,仅在确切知道额外属性是无害的情况下使用。

# 2. 索引签名

为接口添加索引签名,以允许对象包含任意数量的额外属性:

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;  // 允许其他任意属性
}
1
2
3
4
5

这样定义后,SquareConfig 可以包含任意数量的其他属性,并且这些属性的类型可以是 any。

# 3. 变量赋值

将对象字面量赋值给一个变量,然后传递该变量:

let squareOptions = { colour: 'red', width: 100 };
let mySquare = createSquare(squareOptions);  // 不会触发额外属性检查
1
2

这种方法将对象字面量赋值给 squareOptions 变量,避免了对象字面量直接传递时的额外属性检查。

# 最佳实践

  • 避免随意绕过额外属性检查。
  • 如果支持传入不同名称的属性,应修改接口定义以反映这一点。
  • 对于复杂对象,可以使用索引签名来支持额外的动态属性。

# 6. 函数类型

接口不仅可以用来描述对象的结构,还可以用来描述函数的类型。通过在接口中定义调用签名,可以对函数的参数和返回值进行类型检查。

# 函数类型的定义

为了定义函数类型,需要在接口中定义一个调用签名,指定参数列表和返回值类型。

示例:

// 定义一个函数类型的接口 SearchFunc
interface SearchFunc {
    (source: string, subString: string): boolean;  // 调用签名
}
1
2
3
4

# 函数类型的使用

一旦定义了函数类型的接口,就可以像使用其他接口一样使用它。下面是如何创建一个函数类型的变量,并将一个符合该类型的函数赋值给它。

示例:

// 定义一个变量 mySearch,其类型为 SearchFunc
let mySearch: SearchFunc;

// 为 mySearch 赋值一个符合 SearchFunc 类型的函数
mySearch = function(source: string, subString: string): boolean {
    let result = source.search(subString);
    return result > -1;  // 返回布尔值,表示是否找到子字符串
}
1
2
3
4
5
6
7
8

# 参数类型推断

对于函数类型的类型检查,函数的参数名不需要与接口中定义的名字相匹配,只需参数类型匹配即可。此外,TypeScript 会根据赋值的上下文自动推断参数类型。

示例:

let mySearch: SearchFunc;

// 使用推断的参数类型
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}
1
2
3
4
5
6
7

在这个示例中,src 和 sub 参数的类型由 SearchFunc 接口推断出来。函数的返回值类型由返回的布尔值自动推断。

注意事项

  • 参数名匹配:函数参数名不需要与接口中的名称相同,只需类型匹配即可。
  • 类型推断:当函数直接赋值给接口类型的变量时,TypeScript 会自动推断参数和返回值的类型。
  • 类型检查:如果函数返回值的类型不匹配接口定义的类型,类型检查器会发出警告。

通过接口定义函数类型,可以确保函数参数和返回值的一致性,提高代码的可读性和维护性。TypeScript 的类型推断机制也可以简化函数类型的定义过程,使代码更加简洁。

# 7. 可索引的类型

在 TypeScript 中,接口不仅可以用于描述对象和函数的类型,还可以用于描述那些能够通过索引访问的类型,例如数组或字典。可索引类型具有一个索引签名,描述了通过索引获取的值的类型。

# 数字索引签名

数字索引签名用于描述可以通过数字索引获取的元素类型。它类似于数组,常用于表示数组类型的数据结构。

示例:

// 定义一个接口 StringArray,其中数字索引返回 string 类型
interface StringArray {
    [index: number]: string;  // 索引签名,使用数字索引返回 string 类型
}

// 实例化一个 StringArray 类型的变量 myArray
let myArray: StringArray;
myArray = ['Bob', 'Fred'];  // 为 myArray 赋值为一个字符串数组

// 使用数字索引访问数组元素
let myStr: string = myArray[0];  // 访问第一个元素,类型为 string
1
2
3
4
5
6
7
8
9
10
11

在这个例子中,StringArray 接口定义了一个索引签名 [index: number]: string;,表示使用数字索引时返回值的类型是 string。

# 字符串索引签名

字符串索引签名用于描述可以通过字符串索引获取的值的类型。常用于表示对象类型的数据结构。

示例:

class Animal {
    name: string;  // Animal 类包含一个 name 属性
}

class Dog extends Animal {
    breed: string;  // Dog 类继承自 Animal,并添加了一个 breed 属性
}

// 定义一个接口 NotOkay,其中字符串索引返回 Dog 类型
interface NotOkay {
    [x: string]: Dog;  // 字符串索引签名,返回 Dog 类型
    // [x: number]: Animal;  // 错误:数字索引返回值类型必须是字符串索引返回值类型的子类型
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在 TypeScript 中,当同时使用字符串和数字索引时,数字索引返回值的类型必须是字符串索引返回值类型的子类型。因为在 JavaScript 中,所有的数字索引都会被转换为字符串索引。

# 字符串索引的应用

字符串索引签名常用于定义字典模式,并确保所有属性与索引签名返回值类型匹配。

示例:

// 定义一个接口 NumberDictionary,其中字符串索引返回 number 类型
interface NumberDictionary {
    [index: string]: number;  // 字符串索引签名,返回 number 类型
    length: number;           // 合法,length 是 number 类型
    // name: string;          // 错误,name 类型与索引返回值类型不匹配
}
1
2
3
4
5
6

在这个例子中,length 属性符合索引签名的类型要求,但 name 属性不符合。

# 只读索引签名

可以将索引签名设置为只读,防止通过索引修改值。

示例:

// 定义一个接口 ReadonlyStringArray,其中索引签名为只读
interface ReadonlyStringArray {
    readonly [index: number]: string;  // 只读索引签名
}

let myArray: ReadonlyStringArray = ['Alice', 'Bob'];
// myArray[2] = 'Mallory';  // 错误:无法分配到索引为 2 的元素,因为它是只读属性
1
2
3
4
5
6
7

# 8. 类的类型

# 实现接口

在 TypeScript 中,接口可以用来确保类遵循某种结构或契约,类似于 C# 或 Java 中的接口。

示例:

// 定义一个接口 ClockInterface,包含一个 currentTime 属性
interface ClockInterface {
    currentTime: Date;  // currentTime 属性类型为 Date
}

// 定义一个类 Clock,实现 ClockInterface 接口
class Clock implements ClockInterface {
    currentTime: Date;  // 实现接口中的 currentTime 属性

    constructor(h: number, m: number) {  // 构造函数
        this.currentTime = new Date();  // 初始化 currentTime
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 接口方法

接口可以定义方法,并要求实现接口的类提供具体的实现。

示例:

// 定义一个接口 ClockInterface,包含一个 currentTime 属性和一个 setTime 方法
interface ClockInterface {
    currentTime: Date;  // currentTime 属性类型为 Date
    setTime(d: Date): void;  // setTime 方法,接受一个 Date 参数
}

// 定义一个类 Clock,实现 ClockInterface 接口
class Clock implements ClockInterface {
    currentTime: Date;  // 实现接口中的 currentTime 属性

    // 实现接口中的 setTime 方法
    setTime(d: Date) {
        this.currentTime = d;  // 设置 currentTime 为传入的日期
    }

    constructor(h: number, m: number) {  // 构造函数
        this.currentTime = new Date();  // 初始化 currentTime
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 类静态部分与实例部分的区别

在实现接口时,需要区分类的静态部分和实例部分。接口只会检查类的实例部分,不会检查静态部分。

示例:

// 定义一个接口 ClockConstructor,用于构造函数
interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;  // 构造函数签名
}

// 定义一个接口 ClockInterface,用于实例方法
interface ClockInterface {
    tick(): void;  // 实例方法
}

// 定义一个工厂函数 createClock,用于创建 ClockInterface 实例
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);  // 使用构造函数创建实例
}

// 定义一个类 DigitalClock,实现 ClockInterface 接口
class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }  // 构造函数

    tick() {
        console.log('beep beep');  // 实现 tick 方法
    }
}

// 定义一个类 AnalogClock,实现 ClockInterface 接口
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }  // 构造函数

    tick() {
        console.log('tick tock');  // 实现 tick 方法
    }
}

// 使用 createClock 工厂函数创建 DigitalClock 和 AnalogClock 实例
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

在这个示例中,createClock 函数接受一个 ClockConstructor 类型的构造函数,用于创建符合 ClockInterface 的实例。DigitalClock 和 AnalogClock 类都实现了 ClockInterface 接口,并可以通过 createClock 函数创建其实例。

通过接口和类的结合,TypeScript 提供了一种强大的类型约束机制,帮助开发者编写符合契约的类,同时支持良好的面向对象编程实践。

# 9. 继承接口

在 TypeScript 中,接口可以像类一样进行继承。接口继承允许我们将一个接口中的成员复制到另一个接口中,从而实现接口的模块化和重用。这使得接口更加灵活,可以分割成多个可重用的部分。

# 单接口继承

一个接口可以继承另一个接口,从而获得被继承接口的所有属性和方法。

示例:

// 定义一个接口 Shape,包含一个 color 属性
interface Shape {
    color: string;
}

// 定义一个接口 Square,继承自 Shape,并添加了 sideLength 属性
interface Square extends Shape {
    sideLength: number;
}

// 使用断言方式实例化 Square 接口
let square = {} as Square;
square.color = 'blue';       // 为继承的 color 属性赋值
square.sideLength = 10;      // 为 sideLength 属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在这个示例中,Square 接口继承了 Shape 接口,因此 Square 既包含 Shape 的 color 属性,也包含自己的 sideLength 属性。

# 多接口继承

一个接口可以继承多个接口,创建出一个合成接口,包含所有被继承接口的属性和方法。

示例:

// 定义一个接口 Shape,包含一个 color 属性
interface Shape {
    color: string;
}

// 定义一个接口 PenStroke,包含一个 penWidth 属性
interface PenStroke {
    penWidth: number;
}

// 定义一个接口 Square,继承自 Shape 和 PenStroke
interface Square extends Shape, PenStroke {
    sideLength: number;
}

// 使用断言方式实例化 Square 接口
let square = {} as Square;
square.color = 'blue';       // 为继承的 color 属性赋值
square.sideLength = 10;      // 为 sideLength 属性赋值
square.penWidth = 5.0;       // 为继承的 penWidth 属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在这个示例中,Square 接口继承了 Shape 和 PenStroke 接口,因此它包含了 color、penWidth 和 sideLength 属性。

# 10. 混合类型

JavaScript 具有动态灵活的特性,TypeScript 接口可以用来描述复杂的类型,包括那些同时具备多种行为的对象。例如,一个对象可以既是一个函数,又有额外的属性。

# 定义混合类型接口

混合类型接口可以同时描述函数和对象的行为。

示例:

// 定义一个接口 Counter,既可以作为函数调用,又包含 interval 属性和 reset 方法
interface Counter {
    (start: number): string;  // 函数签名
    interval: number;         // 属性
    reset(): void;            // 方法
}

// 定义一个函数 getCounter,返回一个符合 Counter 接口的对象
function getCounter(): Counter {
    let counter = (function (start: number) {
        return 'Counter started at ' + start;
    }) as Counter;  // 类型断言为 Counter

    counter.interval = 123;   // 为 interval 属性赋值
    counter.reset = function () {   // 实现 reset 方法
        console.log('Counter reset');
    };
    return counter;
}

// 获取 Counter 对象,并调用其方法和属性
let c = getCounter();
console.log(c(10));           // 调用函数部分
c.reset();                    // 调用 reset 方法
c.interval = 5.0;             // 访问 interval 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在这个示例中,Counter 接口描述了一个可以作为函数调用的对象,并且还具有 interval 属性和 reset 方法。通过这种方式,可以灵活地为复杂对象定义类型。

# 11. 接口继承类

接口可以继承类的类型。这种情况下,接口会继承类的成员(包括私有成员和受保护成员),但不包括具体实现。当接口继承了一个包含私有成员或受保护成员的类时,只有这个类或其子类能够实现该接口。

# 接口继承类的示例

示例:

// 定义一个类 Control,包含一个私有成员 state
class Control {
    private state: any;  // 私有成员 state
}

// 定义一个接口 SelectableControl,继承自 Control,包含一个 select 方法
interface SelectableControl extends Control {
    select(): void;  // 方法签名
}

// 定义一个类 Button,继承自 Control,并实现 SelectableControl 接口
class Button extends Control implements SelectableControl {
    select() {  // 实现 select 方法
        console.log('Button selected');
    }
}

// 定义一个类 TextBox,继承自 Control,并实现 SelectableControl 接口
class TextBox extends Control implements SelectableControl {
    select() {  // 实现 select 方法
        console.log('TextBox selected');
    }
}

// 错误:类 'ImageC' 中缺少 'state' 属性
// 定义一个类 ImageC,试图实现 SelectableControl 接口,但不继承 Control
class ImageC implements SelectableControl {
    select() {  // 实现 select 方法
        console.log('ImageC selected');
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 关键点

  • 私有和受保护成员:当接口继承了一个类时,会继承该类的私有和受保护成员。这意味着只有该类及其子类能够实现该接口。
  • 强类型约束:通过继承类,接口可以实现对实现类的更强的类型约束。

在这个示例中,SelectableControl 接口继承了 Control 类。因此,只有 Control 的子类才能实现 SelectableControl 接口。Button 和 TextBox 类可以正常实现接口,因为它们继承自 Control,而 ImageC 类不能实现接口,因为它没有继承 Control。

接口继承类是 TypeScript 的一个强大特性,它允许开发者在类与接口之间建立更复杂和灵活的关系,增强类型系统的表达能力。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
TypeScript - 文件打包
TypeScript - 函数

← TypeScript - 文件打包 TypeScript - 函数→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式