介绍
TypeScript 是带有编译时类型检查器的 JavaScript 运行时
相对于js的优点(摘自通义千问,q: ts优点):
- 静态类型检查:TypeScript 引入了静态类型系统,在编译阶段就能发现类型错误,有助于提前捕捉潜在的bug,减少运行时错误,提高了代码质量和稳定性。
- 提升开发效率:由于类型信息的存在,大多数现代IDE和编辑器能为TypeScript提供强大的代码补全、接口提示、重构支持等特性,极大提升了开发效率和体验。
- 可维护性和可读性:类型注释充当了代码的一部分文档,使得阅读和理解代码变得更加容易,特别是在大型项目中,这对于团队协作尤为重要。
- 面向对象编程支持:TypeScript全面支持类、接口、继承、泛型等面向对象编程特性,使得代码组织结构更为清晰,易于管理和扩展。
- 渐进式采用:TypeScript设计为与JavaScript完全兼容,意味着你可以逐步在现有项目中引入TypeScript,无需一次性迁移所有代码。
- 强大的社区与生态系统:TypeScript拥有活跃的社区支持,众多流行的JavaScript库提供了TypeScript定义文件,使得类型安全地使用这些库成为可能。此外,很多现代框架和工具链如Angular、Vue.js和React都官方支持或推荐使用TypeScript。
- 代码安全性:通过类型检查,TypeScript能够在开发阶段排除一些类型不匹配的问题,减少了因类型错误导致的安全漏洞。
- 未来兼容性:TypeScript不断吸收最新的JavaScript语言特性,支持ECMAScript的最新规范,使得开发者能够提前使用未来的JavaScript特性,同时保持代码的向后兼容性。
- 工程化支持:对于大型项目和团队而言,TypeScript的类型系统和工具链支持有助于实施更严格的代码规范和最佳实践,促进代码的模块化和组件化,适合构建可维护的大型应用程序。
类型系统层级
- 顶层类型: 是所有其他类型的父类型:any, unknown(能接受任何类型的值,any允许对器进行任何操作,unknown要求在操作前进行类型断言/检查;所以推荐使用unknown)
- 底层类型:是所有类型的子类型:never(一个永远不会有值的类型,例如一个总是throw error的函数/死循环函数的返回值)
工具类型
详看:
1. type Readonly<T> = {
readonly [P in keyof T]: T[P];};
2. type Partial<T> = {
[P in keyof T]?: T[P];};
3. type Pick<T, K extends keyof T> = {
[P in K]: T[P];};
4. type Pick<T, K extends keyof T> = {
[P in K]: T[P];};
5. type Record<K extends keyof any, T> = {
[P in K]: T;};
6. type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
7. Required<T> 把T中的属性变成必需
8. type T = Exclude<"a" | "b" | "c", "a" | "b">; // T 的类型为 "c"; 返回一个新类型,该新类型包含在 T 中存在但不在 U 中存在的成员类型
9. Omit<T, K> 返回一个新类型,该新类型排 除了类型 T 中指定的**属性** K
.......
条件类型
T extends U ? X : Y
T 是待检查的类型,U 是条件类型,X 是满足条件时返回的类型,Y 是不满足 条件时返回的类型
- infer(推断) infer 关键字用于声明一个类型变量,在条件类型中表示待推断的部分类型。它通常在 条件类型的分支中使用,以便从给定类型中提取和推断出某些信息。 infer 关键字只能在条件类型的右侧使用,每个条件类型只能使用一次 infer 关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;}
type AddReturnValue = ReturnType<typeof add>; // 推断为 number 类型
模板字面量类型
使用模板字面量类型,我们可以创建 基于字符串模板的复杂类型
type Greeting<T extends string> = `Hello, ${T}!`;
type GreetingWorld=Greeting<'World'>; //GreetingWorld的类型为"Hello, World!"
模板字面量类型还支持模板字符串的拼接、条件语句、循环?等操作
type Pluralize<T extends string> = `${T}s`;
type Message<T extends boolean> = T extends true ? 'Enabled' : 'Disabled';
type Plural = Pluralize<'apple'>; // Plural 的 类 型 为 "apples"
type EnabledMessage = Message<true>; // EnabledMessage 的类型为'Enabled'
类型守卫
类型守卫可以用于在运行时检查变量的类型,并在代码块内部将变 量的类型范围缩小到更具体的类型
- typeof
- instanceof
- in
- 真值判断
类型体操
- 条件类型
- keyof 关键字 和 索引(属性)访问类型
interface Person {
name: string;
age: number;}
type PersonKeys = keyof Person; // "name" | "age"
type PersonNameType = Person['name']; // string
- infer(同上条件类型)
类型约束
- extends
function printProperty<T extends { name: string }>(obj: T): void { console.log(obj.name);}
printProperty({ name: 'John', age: 25 }); // 输出 'John'
在上面的示例中,printProperty 函数接受一个泛型参数 T,该参数必须满足一个约束 条件:具有 name 属性,且 name 的类型为 string。通过使用 extends 关键字和类型 约束,我们可以确保 obj 参数具有所需的属性和类型,从而避免出现错误
结构化类型(鸭子类型)
结构化类型系统背后的思想是如果他们的成员类型是兼容的,则他们是兼容的。鸭子类型的概念来自一个古老的英语成语:“如果它走起路来像一只鸭子,叫起来也像 一只鸭子,那么它就是一只鸭子。”在 TypeScript(或更一般地说,静态类型语言)的 上下文中,鸭子类型意味着一个对象的类型不是由它继承或实现的具体类别决定的,而 是由它具有的结构决定的
interface Duck {
walk: () => void;
quack: () => void;
}
function doDuckThings(duck: Duck) {
duck.walk();
duck.quack();
}
const myDuck = {
walk: () => console.log('Walking like a duck'),
quack: () => console.log('Quacking like a duck'),
swim: () => console.log('Swimming like a duck')
};
doDuckThings(myDuck); // OK 只要一个对象的结构满足了接口的要求,我们就可以把这 个对象看作是这个接口的实例,而不管这个对象的实际类型是什么
类型兼容
(ts中类型兼容相当文档: )
any
, unknown
, object
, void
, undefined
, null
, and never
类型兼容性判断如下:
协变与逆变(类型兼容的书面描述(偏公式/定律啥的),大致了解下就行)
下面摘自通义千问(q: 逆变与协变出处,是什么意思):
逆变(Contravariance)和协变(Covariance)这两个术语来源于数学领域,并在计算机科学中被借用,特别是在泛型编程、类型系统以及函数式编程等上下文中有着重要应用。它们描述的是不同类型参数之间的变换规则以及这种变换如何影响复合类型(如数组、函数类型、接口等)之间的子类型关系。
计算机科学中的逆变与协变: 在计算机科学,尤其是类型系统中,逆变与协变主要用来描述泛型类型参数与它们构成的复合类型之间的子类型关系如何随着参数类型的子类型关系变化。
- 协变(Covariance) :如果一个类型构造器(如泛型类、接口或数组类型)允许其类型参数T的子类型S替换T的位置,同时保持该构造器生成的类型间的子类型关系,那么这个类型构造器对于T是协变的。例如,如果
List<Cat>
可以被视为List<Animal>
的子类型(因为Cat是Animal的子类型),那么List<T>
对于T就是协变的。- 逆变(Contravariance) :如果一个类型构造器允许其类型参数T的父类型S替换T的位置,同时保持子类型关系,那么这个类型构造器对于T是逆变的。例如,如果一个接受
Animal
作为参数的函数可以被一个接受Cat
作为参数的函数替代(因为任何接受Cat的函数都必然能处理Animal),那么表示函数类型的构造器对于其输入参数类型是逆变的。 协变与逆变是数学概念,在类型编程中有应用,ts类型兼容问题里体现,主要是 子类型和赋值 时的检查
协变和逆变是对类型比较(类型兼容)一种形式化描述(个人理解:是一个概念,ts类型兼容检查时的一个规则,我们可以利用这个规则)
- 协变:类型的向下兼容性 它描述的是类型的“向下兼容性”。如果一个类型 A 可以被看作是另一个类型 B 的子类型(即 A 可以被安全地用在期望 B 的任何地方), 那么我们就说A 到 B是协变的。
- eg:
- null 和 undefined 类型是所有类型的子类型(
let a:number = undefined;
is ok) - 例如在面向 对象编程中的继承就是协变的一种表现。
class Animal { name: string; } class Dog extends Animal { breed: string; } let myDog: Dog = new Dog(); let myAnimal: Animal = myDog; // OK,因为 Dog 是 Animal 的子类型
- null 和 undefined 类型是所有类型的子类型(
- 逆变:类型的向上兼容性 如果一个类型 A 可以被看作是另一个类型 B 的超 类型(即 B 可以被安全地用在期望 A 的任何地方),那么我们就说 A 到 B 是逆变的。
- eg:
- 在函数参数类型的兼容性检查中,TypeScript 使用了逆变
type Animal = { name: string }; type Dog = Animal & { breed: string }; let dogHandler = (dog: Dog) => { console.log(dog.breed); } let animalHandler: (animal: Animal) => void = dogHandler; // Error! type Handler = (arg: Animal) => void; let animalHandler: Handler = (animal: Animal) => { /* ... */ }; let dogHandler: Handler = (dog: Dog) => { /* ... */ }; // OK,因为 Animal 是 Dog 的超类型
数组元素类型应保持协变,函数参数类型应保持逆变
在某些情况下,我们甚至需要主动破坏类型的协变或逆变,以获得更强的类型安全。例如,如果我们需要向一 个 Dog[]数组中添加 Animal 对象,我们可能需要将这个数组的类型声明为 Animal[], 以防止添加不兼容的类型。
装饰器
详看:
装饰器使用@expression 的形式。其中,expression 必须为一个返回函数的表达式,这个函数在运行时会被调用,传入相关的装饰器参数
- 类装饰器:可以用于观察、修改或替换类定义。类装饰器在应用时,会作为函数调用,并将构造函数作为其唯一的参数
- 方法装饰器:可以用于观察、修改或替换方法定义。当装饰器被调用时,它会接收到三个参数:当前类的原型,方法名,以及该方法的属性描述符
- 访问器装饰器:可以用于观察、修改或替换访问器的 定义
- 属性装饰器:可以用于对属性设置元数据(结合其他地方读取使用)
- 参数装饰器:可以用于对参数设置元数据等等
reflect-metadata
ioc容器(依赖反转/依赖注入)实现基本上都基于 装饰器 和 reflect-metadata 实现。 比如 nestjs里的ioc容器、
参考
- 推荐:
- 通义千问: