TypeScript 基础介绍(二)

引言:从基础到结构化类型

        在《TypeScript 基础介绍(一)》TypeScript基础介绍(一)-CSDN博客中,我们探讨了 TypeScript 的类型系统基础、联合类型、类型断言和类型守卫等核心特性。这些内容解决了 JavaScript 在类型检查和代码可读性方面的基础问题。然而,随着应用规模增长,我们需要更强大的工具来描述复杂对象结构、复用类型定义并实现类型安全的代码复用。本文 fly 将继续深入 TypeScript 的结构化类型系统,包括接口、类型别名、函数类型、泛型及类与接口的结合,通过实战案例展示如何构建健壮且可维护的类型系统。

目录

引言:从基础到结构化类型

六、接口:定义对象的结构契约

6.1 基础接口定义与使用

6.2 接口的索引签名:动态属性名

6.3 接口继承:复用与扩展类型

七、类型别名:创建自定义类型

7.1 基础类型别名

7.2 对象类型别名

八、函数类型:精确描述函数签名

8.1 函数类型表达式

8.2 接口定义函数类型

8.3 函数参数的高级类型

九、泛型:编写可复用的类型安全代码

9.1 泛型函数:适应多种类型

9.2 泛型约束:限制类型范围

9.3 泛型接口与类

十、类与接口:面向对象编程的类型约束

10.1 类实现接口

10.2 类的类型继承与接口实现结合

十一、交叉类型:组合多个类型

十二、类型别名 vs 接口:何时选择哪种方式

12.1 核心区别对比

12.2 最佳实践建议

结语


六、接口:定义对象的结构契约

        接口(Interface)是 TypeScript 中描述对象形状的核心工具,它定义了对象必须包含的属性和方法,是实现代码契约化设计的基础。与基本类型不同,接口专注于描述复杂数据结构,确保不同部分的代码遵循一致的数据格式。

6.1 基础接口定义与使用

       接口通过interface关键字声明,指定对象应包含的属性名称、类型及可选性:

// 定义用户接口
interface User {id: number;          // 必选属性name: string;        // 必选属性age?: number;        // 可选属性(使用?标记)readonly email: string; // 只读属性(初始化后不可修改)
}// 正确实现接口
const validUser: User = {id: 1,name: "Alice",email: "alice@example.com"
};// 错误示例:缺少必选属性id
const invalidUser: User = {name: "Bob",email: "bob@example.com"// ❌ 类型 "{ name: string; email: string; }" 中缺少属性 "id",但类型 "User" 中需要该属性
};// 错误示例:修改只读属性
validUser.email = "new-email@example.com";
// ❌ 无法分配到 "email" ,因为它是只读属性

关键特性

  • 必选属性:默认情况下,接口属性为必填,实现时必须提供
  • 可选属性:通过?标记,允许对象缺少该属性(如age?: number
  • 只读属性:通过readonly关键字,确保属性初始化后不可修改(运行时仍可通过索引修改,但编译时会报错)

6.2 接口的索引签名:动态属性名

        当对象属性名不确定但类型已知时,可使用索引签名定义键值对的类型约束:

// 字符串索引签名:键为string,值为number
interface NumberDictionary {[key: string]: number;length: number; // 允许,因为length是string类型键,值为number// name: string; // ❌ 错误,值类型必须为number
}// 数字索引签名:键为number,值为string
interface StringArray {[index: number]: string;
}const fruits: StringArray = ["apple", "banana", "cherry"];
console.log(fruits[0]); // 输出: "apple"(TypeScript推断为string类型)

应用场景:处理 JSON 数据、配置对象等动态结构,同时保持类型安全。

6.3 接口继承:复用与扩展类型

        接口支持通过extends关键字继承其他接口,实现类型复用和扩展:

// 基础接口
interface Person {name: string;age: number;
}// 继承Person并添加职业属性
interface Employee extends Person {department: string;salary: number;
}// 实现继承后的接口
const employee: Employee = {name: "John",age: 30,department: "Engineering",salary: 80000
};// 多继承:同时继承多个接口
interface Contact {phone: string;
}interface Staff extends Person, Contact {id: number;
}const staff: Staff = {name: "Jane",age: 28,phone: "123-456-7890",id: 1001
};

优势:避免代码重复,构建层次化的类型体系,符合开闭原则(对扩展开放,对修改封闭)。

七、类型别名:创建自定义类型

        类型别名(Type Alias)通过type关键字为已有类型创建新名称,支持基本类型、联合类型、交叉类型等复杂场景,是定义复用类型的灵活工具。

7.1 基础类型别名

        为基本类型或联合类型创建别名,提升代码可读性:

// 为基本类型创建别名
type Age = number;
type Name = string;// 为联合类型创建别名
type Status = "active" | "inactive" | "pending";
type ID = string | number;// 使用类型别名
let userAge: Age = 25;
let userName: Name = "Alice";
let userStatus: Status = "active"; // 只能赋值指定的字符串字面量
let userId: ID = "user-123"; // 合法,string类型
userId = 456; // 合法,number类型// 错误示例:赋值不在联合类型中的值
userStatus = "deleted"; 
// ❌ 类型 ""deleted"" 不能赋值给类型 "Status"

7.2 对象类型别名

        与接口类似,类型别名可描述对象结构,但支持更复杂的组合:

// 对象类型别名
type Point = {x: number;y: number;z?: number; // 可选属性
};// 联合类型别名
type Shape = | { kind: "circle"; radius: number }| { kind: "square"; sideLength: number }| { kind: "triangle"; base: number; height: number };// 使用类型别名计算面积
function calculateArea(shape: Shape): number {switch (shape.kind) {case "circle":return Math.PI * shape.radius ** 2;case "square":return shape.sideLength ** 2;case "triangle":return (shape.base * shape.height) / 2;default://  exhaustive check( exhaustive:彻底的):确保覆盖所有可能的类型const _exhaustiveCheck: never = shape;return _exhaustiveCheck;}
}// 示例调用
console.log(calculateArea({ kind: "circle", radius: 5 })); // 输出: 78.5398...
console.log(calculateArea({ kind: "square", sideLength: 10 })); // 输出: 100

关键特性:支持联合类型、交叉类型等复杂组合,适合描述非对象类型(如基本类型、联合类型)。

八、函数类型:精确描述函数签名

        TypeScript 允许通过函数类型表达式接口定义函数的参数和返回值类型,确保函数调用的类型安全。

8.1 函数类型表达式

        直接在函数变量或参数中定义类型:

// 定义函数类型:(参数1: 类型, 参数2: 类型) => 返回值类型
type AddFunction = (a: number, b: number) => number;// 使用函数类型
const add: AddFunction = (a, b) => a + b;
console.log(add(2, 3)); // 输出: 5// 错误示例:参数类型不匹配
const subtract: AddFunction = (a: string, b: number) => a - b;
// ❌ 不能将类型 "(a: string, b: number) => number" 分配给类型 "AddFunction"// 函数作为参数时的类型
function calculate(operation: AddFunction, x: number, y: number): number {return operation(x, y);
}console.log(calculate(add, 10, 20)); // 输出: 30

8.2 接口定义函数类型

         通过接口的调用签名描述函数结构,适合需要扩展属性的函数:

// 接口定义函数类型(调用签名)
interface GreetFunction {(name: string, greeting?: string): string; // 函数参数和返回值defaultGreeting: string; // 函数额外属性
}// 实现接口
const greet: GreetFunction = (name, greeting = greet.defaultGreeting) => {return `${greeting}, ${name}!`;
};
greet.defaultGreeting = "Hello";// 调用函数
console.log(greet("Alice")); // 输出: "Hello, Alice!"
console.log(greet("Bob", "Hi")); // 输出: "Hi, Bob!"

8.3 函数参数的高级类型

        详细讲解函数参数的类型细节,包括可选参数、默认参数、剩余参数:

// 可选参数:使用?标记,必须放在必选参数之后
function logUser(name: string, age?: number): void {console.log(`Name: ${name}, Age: ${age ?? "Unknown"}`);
}
logUser("Alice"); // 输出: "Name: Alice, Age: Unknown"// 默认参数:指定默认值,自动成为可选参数
function createUser(name: string, role: string = "user"): { name: string; role: string } {return { name, role };
}
console.log(createUser("Bob")); // 输出: { name: "Bob", role: "user" }// 剩余参数:使用...收集多个参数为数组
function sum(...numbers: number[]): number {return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 输出: 10

         最佳实践:为所有公共函数添加完整的类型注解,特别是参数和返回值类型,提升代码可读性和重构安全性。

九、泛型:编写可复用的类型安全代码

        泛型(Generics)是 TypeScript 实现类型复用的核心机制,允许在定义函数、接口或类时不指定具体类型,而在使用时动态指定,实现 "一次定义,多种类型复用"。

9.1 泛型函数:适应多种类型

        定义一个可处理不同类型数据的函数,同时保持类型安全:

// 泛型函数:T是类型变量,代表传入的类型
function identity<T>(arg: T): T {return arg; // 返回与输入相同类型的值
}// 使用泛型函数(显式指定类型)
const num: number = identity<number>(42);
const str: string = identity<string>("hello");// 类型推断(推荐):TypeScript自动推断T为传入的类型
const bool = identity(true); // T被推断为boolean类型// 泛型函数示例:获取数组第一个元素
function getFirstElement<T>(array: T[]): T | undefined {return array[0];
}// 使用示例
const numbers = [1, 2, 3];
const firstNum = getFirstElement(numbers); // 推断为number | undefined
console.log(firstNum); // 输出: 1const strings = ["a", "b", "c"];
const firstStr = getFirstElement(strings); // 推断为string | undefined

9.2 泛型约束:限制类型范围

        使用extends关键字约束泛型只能是特定类型或具有特定属性:

// 泛型约束:T必须具有length属性
interface Lengthwise {length: number;
}function logLength<T extends Lengthwise>(arg: T): T {console.log(`Length: ${arg.length}`);return arg;
}logLength("hello"); // 输出: "Length: 5"(string有length属性)
logLength([1, 2, 3]); // 输出: "Length: 3"(数组有length属性)
// logLength(42); // ❌ 错误,number没有length属性// 泛型约束:使用keyof获取对象键的联合类型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];
}const user = { name: "Alice", age: 25 };
console.log(getProperty(user, "name")); // 输出: "Alice"(类型为string)
console.log(getProperty(user, "age")); // 输出: 25(类型为number)
// getProperty(user, "email"); // ❌ 错误,"email"不是user的键

9.3 泛型接口与类

        将泛型应用于接口和类,创建可复用的类型组件:

// 泛型接口
interface Box<T> {value: T;getValue: () => T;
}// 实现泛型接口
const numberBox: Box<number> = {value: 100,getValue: () => numberBox.value
};const stringBox: Box<string> = {value: "TypeScript",getValue: () => stringBox.value
};// 泛型类
class Stack<T> {private items: T[] = [];push(item: T): void {this.items.push(item);}pop(): T | undefined {return this.items.pop();}
}// 使用泛型类
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 输出: 2const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.pop()); // 输出: "b"

        泛型的价值:在保持类型安全的同时,大幅提升代码复用性,是开发通用库和组件的基础。

十、类与接口:面向对象编程的类型约束

        TypeScript 结合了面向对象编程和类型系统,允许通过接口约束类的实现,确保类遵循特定的结构。

10.1 类实现接口

        使用implements关键字使类遵循接口定义的结构:

// 定义接口
interface Animal {name: string;makeSound(): void;
}// 类实现接口
class Dog implements Animal {name: string;constructor(name: string) {this.name = name;}makeSound(): void {console.log("Woof!");}// 类可以有接口之外的方法fetch(): void {console.log(`${this.name} is fetching the ball`);}
}// 实例化类
const dog = new Dog("Buddy");
dog.makeSound(); // 输出: "Woof!"
dog.fetch(); // 输出: "Buddy is fetching the ball"// 错误示例:未实现接口的方法
class Cat implements Animal {name: string;constructor(name: string) {this.name = name;}// ❌ 错误,Cat类缺少"makeSound"方法的实现
}

10.2 类的类型继承与接口实现结合

        类可以同时继承另一个类并实现接口,实现代码复用和接口约束的双重目的:

// 基础类
class Vehicle {speed: number;constructor(speed: number) {this.speed = speed;}move(): void {console.log(`Moving at ${this.speed} km/h`);}
}// 接口
interface Flyable {altitude: number;fly(): void;
}// 继承类并实现接口
class Airplane extends Vehicle implements Flyable {altitude: number;constructor(speed: number, altitude: number) {super(speed); // 调用父类构造函数this.altitude = altitude;}// 重写父类方法move(): void {console.log(`Flying at ${this.speed} km/h and ${this.altitude} m altitude`);}// 实现接口方法fly(): void {console.log("Taking off!");}
}// 使用类
const plane = new Airplane(900, 10000);
plane.fly(); // 输出: "Taking off!"
plane.move(); // 输出: "Flying at 900 km/h and 10000 m altitude"

十一、交叉类型:组合多个类型

        交叉类型(Intersection Types)使用&符号将多个类型合并为一个,新类型包含所有类型的属性和方法,适用于组合对象结构。

// 定义两个接口
interface HasName {name: string;
}interface HasAge {age: number;
}// 交叉类型:同时包含HasName和HasAge的属性
type Person = HasName & HasAge;// 使用交叉类型
const person: Person = {name: "Alice",age: 25
};// 交叉类型与联合类型的区别
type A = { a: number } & { b: string }; // 必须同时有a和b
type B = { a: number } | { b: string }; // 可以有a或b或两者都有// 复杂交叉类型示例
type WithId = { id: string };
type User = HasName & HasAge & WithId;const user: User = {id: "user-123",name: "Bob",age: 30
};

注意:交叉类型不适合基本类型组合(如string & number会得到never类型,因为没有值同时是 string 和 number)。

十二、类型别名 vs 接口:何时选择哪种方式

        类型别名和接口都可用于定义对象结构,但在使用场景上有明确区别,选择正确的工具能提升代码清晰度和可维护性。

12.1 核心区别对比

特性类型别名(type)接口(interface)
定义范围可描述任意类型(对象、联合、基本类型等)主要用于描述对象结构和函数类型
扩展方式通过交叉类型(type A = B & { ... }通过继承(interface A extends B
合并声明不支持重复声明合并支持重复声明自动合并
计算属性支持(如type Keys = keyof T支持,但语法更复杂

12.2 最佳实践建议

  • 优先使用接口当:

    • 定义对象结构且需要继承或被继承
    • 需要自动合并声明(如扩展第三方库类型)
    • 描述类的公共 API(更符合面向对象思维)
  • 优先使用类型别名当:

    • 定义联合类型、交叉类型或基本类型别名
    • 描述元组类型(如type Point = [number, number]
    • 需要使用计算属性或条件类型
// 接口合并示例(接口特有)
interface Config {apiUrl: string;
}interface Config {timeout: number;
}// 自动合并为 { apiUrl: string; timeout: number }
const config: Config = {apiUrl: "https://api.example.com",timeout: 5000
};// 类型别名不支持合并
type Settings = { theme: string };
// type Settings = { layout: string }; // ❌ 错误,重复的标识符"Settings"

结语

        本文深入探讨了 TypeScript 的结构化类型特性,包括接口、类型别名、函数类型、泛型、类与接口的结合及交叉类型等核心概念。这些工具共同构成了 TypeScript 强大的类型系统,使开发者能够构建类型安全、可维护且高度复用的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917015.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917015.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【科研绘图系列】R语言绘制线性相关性

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图 系统信息 参考 介绍 【科研绘图系列】R语言绘制线性相关性 加载R包 library(tidyverse) library(ggplot2) library(ggsignif) library(RColorBrewer) library(dplyr) library(reshape2

FastAPI的请求-响应周期为何需要后台任务分离?

url: /posts/c7b54d6b3b6b5041654e69e5610bf3b9/ title: FastAPI的请求-响应周期为何需要后台任务分离? date: 2025-07-31T06:11:25+08:00 lastmod: 2025-07-31T06:11:25+08:00 author: cmdragon summary: FastAPI 的请求-响应周期遵循 ASGI 协议,类似于餐厅点餐流程。同步处…

多种录音笔录音芯片方案推荐

多种录音笔录音芯片方案推荐一、引言随着信息技术的飞速发展&#xff0c;录音笔作为一种重要的音频记录设备&#xff0c;在会议记录、采访、学习等众多场景中得到广泛应用。其核心的录音芯片方案直接影响录音质量、功能特性以及产品成本。唯创知音作为音频芯片领域的知名厂商&a…

Linux系统编程Day2-- Linux常用操作

一、Linux 基本命令概览以下是一些常用的Linux命令操作&#xff0c;后续我们会对其每个单独如何使用进行讲解。操作类型常用命令示例文件/目录操作ls, cd, cp, mv, rm, mkdir, rmdir查看文件内容cat, less, more, head, tail查找操作find, grep, locate, which权限管理chmod, c…

cs336 assignment1 作业环境配置

代码结构 所有的代码写到cs336_basics/* 下面&#xff0c;在adapters.py里调用自己的.py&#xff0c;通过所有的test。 作业资料参考 karpathy视频仓库&#xff1a; 视频 github仓库 测试项目运行环境 下载uv uv官网仓库 使用命令&#xff1a; powershell -ExecutionPoli…

YOLOv11来了,使用YOLOv11训练自己的数据集和推理(附YOLOv11网络结构图)

文章目录前言一、YOLOv11代码下载地址1.YOLOv11模型结构图二、数据集准备1.数据集标注软件2.voc数据集格式转换3.数据集划分4.修改yolo的训练配置文件三、YOLO环境配置教程1.pytorch环境安装2.其他依赖安装四、YOLOv11训练五、YOLOv11推理六、解决训练过程中断怎么继续上次训练…

20250731在荣品的PRO-RK3566开发板的Android13下跑通敦泰的FT8206触控芯片

20250731在荣品的PRO-RK3566开发板的Android13下跑通敦泰的FT8206触控芯片 2025/7/31 17:48缘起&#xff1a;本文前置条件&#xff1a;已经解决FT8206和PRO-RK3566的硬件连接。 通过i2cdect可以扫描到i2c从机地址&#xff1a;0x38。【8位地址为0x70】缘起&#xff1a;本文只分析…

异常检测:算法分类及经典模型概览

第一部分&#xff1a;异常检测的核心概念 在深入算法细节之前&#xff0c;理解异常检测的“语境”至关重要。 1. 什么是异常检测&#xff1f; 异常检测&#xff08;Anomaly Detection 或 Outlier Detection&#xff09;旨在通过数据挖掘技术&#xff0c;识别出数据集中与大多数…

技术干货 | 矢网DTF测量技术:透视线缆、天线与波导内部缺陷的“射频X光”(二)

无线通信、雷达等领域中&#xff0c;射频组件与传输系统的性能至关重要&#xff0c;其内部微小损伤易导致信号问题甚至系统失效。传统测试无法精确定位故障点&#xff0c;排查困难。DTF测量&#xff0c;矢网赋予的“透视眼”&#xff01;它能穿透“黑箱”&#xff0c;精确定位线…

【[CSP-J 2022] 上升点列】

题目 [CSP-J 2022] 上升点列 题目描述 在一个二维平面内&#xff0c;给定 n 个整数点 (x i ,y i​ )&#xff0c;此外你还可以自由添加 k 个整数点。 你在自由添加 k 个点后&#xff0c;还需要从 nk 个点中选出若干个整数点并组成一个序列&#xff0c;使得序列中任意相邻两点间…

Kong API Gateway的十年进化史

一、技术基因的诞生&#xff08;2007-2015&#xff09; 2007年&#xff0c;三位意大利开发者Augusto Marietti、Marco Palladino和Michele Orru在博洛尼亚的一个小车库中创立了Mashape公司。 最初他们开发了一个名为Mashup的API聚合平台&#xff0c;试图通过整合第三方API为开发…

蓝牙设备配对:从机发现主机全过程

在蓝牙 paging 过程中&#xff0c;从设备&#xff08;Slave&#xff09;是通过特定的扫描机制和跳频方式来发现主设备发送的 ID 包的&#xff0c;具体过程如下&#xff1a;从设备处于特定扫描模式&#xff1a;从设备需要处于 Page Scan 模式&#xff0c;才能够接收主设备发送的…

聚观早报 | 三星获特斯拉AI芯片订单;小米16首发成安卓最强SOC;iPhone 17 Pro支持8倍光学变焦

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。整理丨肖羽7月29日消息三星获特斯拉AI芯片订单小米16首发成安卓最强SOCiPhone 17 Pro支持8倍光学变焦宁德时代滑板底盘公司启动首轮融…

Gemini Fullstack LangGraph Quickstart(DeepSeek+Tavily版本)

文章目录参考资料说明Gemini Fullstack LangGraph QuickstartDeepSeek Fullstack LangGraph Quickstart项目部署完整源码地址后端部署前端部署参考资料 DeepResearch应用开发实战网盘课件资料 说明 本文仅供学习和交流使用&#xff0c;感谢赋范社区相关老师的辛苦付出&#…

钢筋计数误差↓78%!陌讯多模态融合算法在建筑地产AI质检的落地实践

​摘要​​针对建筑地产行业钢筋验收场景的高误差痛点&#xff0c;本文解析陌讯视觉算法的多模态融合架构如何实现毫米级精度目标检测。实测显示&#xff1a;在Jetson Xavier NX边缘设备上&#xff0c;钢筋计数mAP0.5达​​92.4%​​&#xff0c;较基线模型提升28个百分点&…

负载均衡 LoadBalance

问题引入 我们一个服务可能会进行多机部署&#xff0c;也就说多台服务器组成的集群共同对外提供一致的服务&#xff0c;那么我们的微服务的代码就需要拷贝多份&#xff0c;部署到不同的机器上。 我们使用 IDEA 来开启多个相同的服务 这里以 product-service 为例&#xff1a;…

13. 若依框架中的 Sensitive 敏感字段过滤

若依框架中有Sensitive注解&#xff0c;但代码中并未使用&#xff0c;但该注解的实现还是比较值的学习的。该注解是一个运行时注解该注解只能应用在字段上JacksonAnnotationsInside 表示当使用Jackson序列化时&#xff0c;Jackson会自动识别该注解下的其他Jackson相关注解&…

git本地仓库,工作区和暂存区的知识

一 git工作原理 Git 的工作原理基于分布式版本控制&#xff0c;通过管理文件的不同版本状态&#xff0c;实现代码的追踪、协作和回溯。除了常见的工作区&#xff08;Working Directory&#xff09; 和暂存区&#xff08;Staging Area/Index&#xff09;&#xff0c;核心还包括本…

MPU6050模块

一&#xff1a;MPU6050简介输出一个随姿态变化而变化的电压&#xff0c;想要量化电压&#xff0c;就得使用ADC转化欧拉角偏航角&#xff08;Yaw&#xff09;&#xff1a;也叫航向角&#xff0c;通常是绕 z 轴旋转的角度&#xff0c;以 x 轴正向为起始边&#xff0c;旋转后 x 轴…

jvm的栈和堆

在 JVM 中&#xff0c;栈&#xff08;Stack&#xff09;和堆&#xff08;Heap&#xff09;是两种核心内存区域&#xff0c;用于存储不同类型的数据&#xff0c;它们的设计和存储规则有明确区分&#xff0c;主要体现在存储内容、生命周期和管理方式上&#xff1a;一、栈&#xf…