1、www.coding-www.coding-卷首语可能是市面上比较好的 Typescript 高级教程,适合有一定 Typescipt 基础的同学学习。随着 TypeScript 的日益普及,它已经成为现代 Web 开发的重要工具。然而,尽管TypeScript 初学者可以轻松上手并开始编写代码,但深入理解 TypeScript 的强大功能和高级特性却是一项更具挑战性的任务。现代 TypeScript 高级教程就是为了帮助您解开 TypeScript 的高级秘密而编写的。在这本教程中,我们将深入探讨 TypeScript 的复杂特性,包括装饰器、泛型、高级类型以及元数据反射等。我们不仅会详细解
2、释这些概念,还会展示如何在实际项目中运用这些高级特性,提供丰富的代码示例和最佳实践,帮助您更好地理解这些复杂的概念。这本教程适合有一定 TypeScript 基础,希望进一步提升技能的开发者。每一章都设计得既可以独立阅读,也可以作为整个教程的一部分。我们深信,无论您是希望对TypeScript 有更深入的了解,还是希望提升在大型项目中使用 TypeScript 的技巧,本教程都将为您提供极大的帮助。关于我笔名 linwu,一枚前端开发工程师,曾入职腾讯等多家知名互联网公司,后面我会持续分享精品课程,欢迎持续关注www.coding-目录页基础.5一、概述.5二、类型.7三、函数.13四、接口和
3、类.17五、枚举和泛型.21六、命名空间和模块.25进阶.29七、类型系统层级.29八、高级类型.32九、类型推断.38十、类型守卫.41十一、泛型和类型体操.49十二、类型兼容:结构化类型.57十三、类型兼容:协变和逆变.63十四、扩展类型定义.67十五、装饰器与反射元数据.73十六、解读 TSConfig.77实战.85十七、TypeScript 封装 Fetch.87十八、TS 实战之扑克牌排序.94一、概述www.coding-5 基础一、概述引言在 TypeScript 的发展过程中,对类型系统的持续改进一直是其核心任务。这在 2.0 版本中引入的严格的空值检查(-strictNul
4、lChecks)中体现得尤为明显,这个功能帮助开发者在编译时捕获可能的 null 或 undefined 引用错误。TypeScript 2.1 带来了映射类型,这是一种创建新类型的方式,基于旧类型转换其属性。2.8 版本则引入了有条件的类型,使得类型系统具备了更多的表达力。TypeScript 3.0 引入了项目引用,这是一种新的架构工具,允许大型项目更容易地组织代码和依赖项。3.7 版本中,TypeScript 支持了可选链和空值合并运算符,这是两个常用的 JavaScript 特性。在最新的 TypeScript 版本中,提供了更丰富的语法特性和工具支持,比如更强大的类型推导,更精确的类
5、型检查,以及更完善的 IDE 支持。优势TypeScript 的优势还包括它的可互操作性。由于 TypeScript 是 JavaScript 的超集,所以开发者可以轻松地将 JavaScript 代码迁移到 TypeScript。同时,开发者还可以使用来自 JavaScript 生态系统的库和工具。TypeScript 还支持最新的 ECMAScript 特性,如箭头函数、模块、解构等。TypeScript 也为大型项目提供了必要的工具。TypeScript 的类型系统使得开发者可以明确定义对象和函数的结构,这样在大型项目中维护和理解代码就更加简单。此外,TypeScript 还有良好的工具
6、支持,比如 TSLint 和 Prettier,这些工具可以帮助开发者编一、概述www.coding-6写更一致、更可读的代码。在性能方面,由于 TypeScript 在运行前进行编译,因此可以提前发现并修复很多可能在运行时才会出现的错误。这种预编译的方式可以大大提高应用程序的性能,因为运行时需要进行的工作量较少。TypeScript 的类型定义文件(.d.ts)是一个独特的优点,它们为已有的 JavaScript 库提供类型信息。这使得开发者可以在使用这些库的同时享受到类型检查的好处。而且,由于有大量的开源贡献者,绝大多数流行的 JavaScript 库都有相应的类型定义文件。总的来说,Ty
7、peScript 结合了 JavaScript 的灵活性和静态类型语言的安全性,使得它成为了现代 Web 开发的重要工具。二、类型www.coding-7二、类型TypeScript 提供了 JavaScript 的所有基本数据类型,如:numbernumber、stringstring、booleaboolean n等。它还增加了额外的类型,比如anyany、unknownunknown、nevernever、voidvoid等。1.number在 TypeScript 中,所有的数字都是浮点数。这些数字的类型是numbernumber。下面是一些例子:let decimal:number=
8、6;/十进制let hex:number=0 xf00d;/十六进制let binary:number=0b1010;/二进制let octal:number=0o744;/八进制2.stringstringstring类型表示文本数据。你可以使用单引号()或双引号()定义字符串,也可以使用反引号()定义模板字符串:let color:string=blue;color=red;let fullName:string=Bob Bobbington;let age:number=37;let sentence:string=Hello,my name is$fullName.Ill be$age
9、+1 years old next month.;3.booleanbooleanboolean类型有两个值:truetrue和falsefalse:let isDone:boolean=false;二、类型www.coding-84.Array在 TypeScript 中,数组类型有两种表达方式。一种是在元素类型后面加上,表示由此类型元素组成的一个数组。另一种方式是使用数组泛型,ArrayArray:let list:number=1,2,3;/或let list:Array=1,2,3;5.Tuple元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值
10、分别为stringstring和numbernumber的元组:let x:string,number;x=hello,10;/OK以上是 TypeScript 的一些基本类型。在接下来的对话中,我们可以进一步讨论其他的TypeScript 类型,比如枚举(enumenum)、nullnull、undefinedundefined、nevernever、voidvoid以及对象类型。6.EnumEnum 是一种特殊的类型,它可以更容易地处理一组有名字的常量。在 TypeScript 中,enum 的默认起始值是 0,然后每个成员的值都会依次增加。你也可以手动为 enum 的成员指定值:enum
11、 Color Red,Green,Bluelet c:Color=Color.Green;/手动指定成员的数值enum Color Red=1,Green=2,Blue=4let c:Color=Color.Green;二、类型www.coding-97.Null and Undefined在 TypeScript 中,undefinedundefined和nullnull各自有自己的类型,分别是undefinedundefined和nullnull。默认情况下,它们是所有类型的子类型。这意味着你可以把nullnull和undefinedundefined赋值给numbernumber类型的变
12、量。然而,当你指定了-strictNullChecks-strictNullChecks标记,nullnull和undefinedundefined只能赋值给voidvoid和它们各自的类型:let u:undefined=undefined;let n:null=null;8.Any当你不确定一个变量应该是什么类型的时候,你可能需要用到anyany类型。anyany类型的变量允许你对它进行任何操作,它就像是 TypeScript 类型系统的一个逃生窗口:let notSure:any=4;notSure=maybe a string instead;notSure=false;/okay,d
13、efinitely a boolean9.Unknownunknowunknown n类型是 TypeScript 3.0 中引入的一种新类型,它是 any 类型对应的安全类型。unknownunknown类型的变量只能被赋值给anyany类型和unknownunknown类型本身:let value:unknown;value=true;/OKvalue=42;/OKvalue=Hello World;/OKvalue=;/OKvalue=;/OK二、类型www.coding-1010.Nevernevernever类型表示的是那些永不存在的值的类型。例如,nevernever类型是那些总是
14、会抛出异常或者根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型:function error(message:string):never throw new Error(message);11.void在 TypeScript 中,voidvoid类型用于表示没有返回值的函数的返回类型。在 JavaScript 中,当一个函数没有返回任何值时,它会隐式地返回undefinedundefined。voidvoid类型就是用来表达这种情况的:function warnUser():void console.log(This is my warning message);在这个例子中,wa
15、rnUserwarnUser函数没有返回任何值,所以它的返回类型是voidvoid。需要注意的是,在 TypeScript 中,你可以声明一个voidvoid类型的变量,但是你只能为它赋予undefinedundefined和nullnull(在非严格 null 检查模式下):let unusable:void=undefined;通常情况下,我们不会这样使用voidvoid类型,因为除了undefinedundefined和nullnull之外,你不能将任何值赋给voidvoid类型的变量。12.联合类型(Union Types)TypeScript 联合类型是一种将多种类型组合到一起的方式
16、,表示一个值可以是多种类型之一。你可以使用管道符(|)来分隔每个类型。这可以让你在不确定一个值是什么类型的时候,为它选择多个可能的类型。二、类型www.coding-11例如,假设我们有一个函数,这个函数的参数可以是numbernumber类型或者stringstring类型:function display(input:string|number)console.log(input);在上面的函数中,我们声明了inputinput参数可以是stringstring类型或者numbernumber类型。你可以传递一个stringstring类型或者numbernumber类型的值给displa
17、ydisplay函数,而 TypeScript 编译器不会报错:display(1);/OKdisplay(Hello);/OK你也可以将联合类型用于变量和属性。例如:let variable:string|number;variable=Hello;/OKvariable=1;/OK在上面的代码中,我们声明了variablevariable可以是stringstring类型或者numbernumber类型。然后我们可以安全地将一个字符串或者数字赋值给variablevariable。联合类型在 TypeScript 中非常常用,因为它们可以帮助你编写更灵活的代码。13.交叉类型(Inters
18、ection Types)交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起获得所需的能力。它被定义为Type1Type1&Type2Type2&Type3Type3&.&TypeNTypeN。它包含了所需的所有类型的成员。interface Part1 a:string;interface Part2 b:number;二、类型www.coding-12type Combined=Part1&Part2;let obj:Combined=a:hello,b:42,;14.类型别名(Type Aliases)类型别名是给一个类型起个新名字。类型别名有时和接口很相似,但可
19、以作用于原始值,联合类型,交叉类型等任何我们需要手写的地方。type MyBool=true|false;type StringOrNumber=string|number;15.字符串字面量类型(String Literal Types)字符串字面量类型允许你指定字符串必须的固定值。在实践中,这种类型常与联合类型、类型别名和类型保护结合使用。type Easing=ease-in|ease-out|ease-in-out;class UIElement animate(dx:number,dy:number,easing:Easing)/.let button=new UIElement()
20、;button.animate(0,0,ease-in);/OKbutton.animate(0,0,uneasy);/Error:uneasy is not allowed here三、函数www.coding-13三、函数TypeScript 提供了丰富的函数类型定义方式,可以对函数参数、返回值进行类型注解,从而提供了更为强大的类型检查。1.函数声明在 TypeScript 中,你可以在函数声明中对函数的参数和返回值进行类型注解。以下是一个例子:function add(x:number,y:number):number return x+y;在这个例子中,我们定义了一个addadd函数,
21、它接受两个参数x x和y y,这两个参数都是numbernumber类型,函数的返回值也是numbernumber类型。如果你尝试调用这个函数并传入一个非数字类型的参数,TypeScript 编译器会报错:add(Hello,1);/Error:Argument of type Hello is not assignableto parameter of type number.2.函数表达式在 JavaScript 中,函数不仅可以通过函数声明的方式定义,还可以通过函数表达式定义。在 TypeScript 中,函数表达式也可以使用类型注解:let myAdd:(x:number,y:numb
22、er)=number=function(x:number,y:number):number return x+y;在上面的例子中,我们首先定义了myAddmyAdd变量的类型为一个函数类型(x:(x:number,number,y:y:三、函数www.coding-14number)number)=numbernumber,然后将一个匿名函数赋值给myAddmyAdd。这个匿名函数的参数x x和y y的类型是numbernumber,返回值的类型也是numbernumber。3.可选参数和默认参数TypeScript 支持可选参数和默认参数。你可以使用?来标记可选参数,或者使用=来指定参数的默
23、认值:function buildName(firstName:string,lastName?:string)if(lastName)return firstName+lastName;elsereturn firstName;let result1=buildName(Bob);/works correctly nowlet result2=buildName(Bob,Adams);/ah,just right在上面的例子中,lastNamelastName是一个可选参数。你可以不传这个参数调用buildNambuildName e函数。function buildName(firstNa
24、me:string,lastName=Smith)return firstName+lastName;let result1=buildName(Bob);/returns Bob Smithlet result2=buildName(Bob,Adams);/returns Bob Adams在上面的例子中,lastNamelastName有一个默认值SmithSmith。如果你不传这个参数调用buildNamebuildName函数,lastNamelastName的值将是SmithSmith。4.剩余参数(Rest Parameters)当你不知道要操作的函数会有多少个参数时,TypeSc
25、ript 提供了剩余参数的概念。与JavaScript 一样,你可以使用三个点.定义剩余参数:三、函数www.coding-15function buildName(firstName:string,.restOfName:string)return firstName+restOfName.join();letemployeeName=buildName(Joseph,Samuel,Lucas,MacKinzie);在上面的例子中,restOfNamerestOfName就是剩余参数,它可以接受任意数量的参数。5.this 和箭头函数箭头函数可以保留函数创建时的thisthis值,而不是调用
26、时的值。在TypeScriptTypeScript中,你可以使用箭头函数来确保thisthis的值:let deck=suits:hearts,spades,clubs,diamonds,cards:Array(52),createCardPicker:function()return()=let pickedCard=Math.floor(Math.random()*52);let pickedSuit=Math.floor(pickedCard/13);return suit:this.suitspickedSuit,card:pickedCard%13;letcardPicker=dec
27、k.createCardPicker();letpickedCard=cardPicker();alert(card:+pickedCard.card+of +pickedCard.suit);在上面的例子中,createCardPickercreateCardPicker函数返回一个箭头函数,这个箭头函数可以访问创建时的thisthis值。三、函数www.coding-166.重载在 JavaScript 中,根据传入不同的参数调用同一个函数,返回不同类型的值是常见的情况。TypeScript 通过为同一个函数提供多个函数类型定义来实现这个功能:function reverse(x:numb
28、er):number;function reverse(x:string):string;function reverse(x:number|string):number|string if(typeof x=number)return Number(x.toString().split().reverse().join();else if(typeof x=string)return x.split().reverse().join();reverse(12345);/returns54321reverse(hello);/returnsolleh在上面的例子中,我们定义了两个重载:一个是接
29、受numbernumber类型的参数,另一个是接受stringstring类型的参数。然后我们在实现函数中处理了这两种情况。四、接口和类www.coding-17四、接口和类在TypeScript 中,接口(Interfaces)和类(Classes)是实现面向对象编程(Object-Oriented Programming,OOP)的基础工具。这些工具提供了一种方式来定义和组织复杂的数据结构和行为。1.接口接口在 TypeScript 中扮演着关键的角色,用于强类型系统的支持。接口可以描述对象的形状,使我们可以编写出预期的行为。接口可用于描述对象、函数或者类的公共部分。以下是一个基本的接口示
30、例:interface LabelledValue label:string;function printLabel(labelledObj:LabelledValue)console.log(labelledObj.label);let myObj=size:10,label:Size 10 Object;printLabel(myObj);在这个例子中,LabelledValueLabelledValue接口就像一个名片,告诉其他代码,只要一个对象有labellabel属性,并且类型为stringstring,那么就可以认为它是LabelledValueLabelledValue。接口也可
31、以描述函数类型:interface SearchFunc(source:string,subString:string):boolean;let mySearch:SearchFunc;mySearch=function(src:string,sub:string):boolean let result=src.search(sub);return result -1;四、接口和类www.coding-18此外,接口还能用于描述数组和索引类型:interface StringArray index:number:string;let myArray:StringArray;myArray=Bo
32、b,Fred;interface Dictionary index:string:string;let myDict:Dictionary;myDict=key:value;2.类与传统的 JavaScript 一样,TypeScript 也使用类(Classes)来定义对象的行为。然而,TypeScript 的类具有一些额外的特性,如访问修饰符(Access Modifiers)、静态属性(Static Properties)、抽象类(Abstract Classes)等。以下是一个基本的类示例:class Greeter greeting:string;constructor(messag
33、e:string)this.greeting=message;greet()return Hello,+this.greeting;let greeter=new Greeter(world);TypeScript 还引入了访问修饰符publicpublic、privateprivate和protectedprotected。如果没有指定访问修饰符,则默认为publicpublic。四、接口和类www.coding-19class Animal private name:string;constructor(theName:string)this.name=theName;TypeScript
34、 类还支持继承,通过extendsextends关键字可以创建一个子类。子类可以访问和改变父类的属性和方法:class Animal name:string;constructor(theName:string)this.name=theName;move(distanceInMeters:number=0)console.log($this.name moved$distanceInMetersm.);class Dog extends Animal constructor(name:string)super(name);bark()console.log(Woof!Woof!);const
35、 dog=new Dog(Tom);dog.bark();dog.move(10);dog.bark();为了实现多态,TypeScript 提供了抽象类的概念。抽象类是不能被实例化的类,只能作为其他类的基类。抽象类中可以定义抽象方法,抽象方法必须在派生类中实现:abstract class Animal abstract makeSound():void;move():void console.log(roaming the earth.);class Dog extends Animal 四、接口和类www.coding-20makeSound()console.log(Woof!Woof
36、!);const myDog=new Dog();myDog.makeSound();myDog.move();五、枚举和泛型www.coding-21五、枚举和泛型接下来我们将学习 TypeScript 中的两个重要主题:枚举(Enums)和泛型(Generics)。这两个特性能大大提高代码的可重用性和安全性。1.枚举枚举是 TypeScript 中一种特殊的数据类型,允许我们为一组数值设定友好的名字。枚举的定义使用enumenum关键字。enum Direction Up=1,Down,Left,Right,在这个例子中,我们定义了一个名为DirectionDirection的枚举,它有四
37、个成员:UpUp、DownDown、LeftLeft和RightRight。UpUp的初始值为 1,其余成员的值会自动递增。除了使用数值,我们也可以使用字符串:enum Direction Up=UP,Down=DOWN,Left=LEFT,Right=RIGHT,此外,TypeScript 还支持计算的和常量成员。常量枚举通过 const enum 进行定义,TypeScript 会在编译阶段进行优化:const enum Enum A=1,B=A*2五、枚举和泛型www.coding-222.异构枚举TypeScript 支持数字和字符串混用的枚举,这种类型的枚举被称为异构枚举:enum
38、BooleanLikeHeterogeneousEnum No=0,Yes=YES,尽管 TypeScript 支持这种用法,但我们在实际项目中应尽可能避免使用异构枚举,因为这会引入不必要的复杂性。3.枚举成员的类型在某些特殊的情况下,枚举成员本身也可以作为一种类型:enum ShapeKind Circle,Square,interface Circle kind:ShapeKind.Circle;radius:number;interface Square kind:ShapeKind.Square;sideLength:number;4.泛型在 TypeScript 中,泛型(Gener
39、ics)是一种强大的类型工具,它允许我们编写可重用、灵活和类型安全的代码。泛型允许我们在定义函数、类或接口时使用类型参数,这些类型参数在使用时可以被动态地指定具体的类型。以下是泛型在 TypeScript 中的几个常见应用场景:五、枚举和泛型www.coding-231.函数泛型函数泛型允许我们编写可适用于多种类型的函数,提高代码的重用性和灵活性。例如:function identity(arg:T):T return arg;let result=identity(42);/result 的类型为 number在上面的示例中,identityidentity函数接受一个类型参数 T,表示输入
40、和输出的类型。通过在函数调用时显式指定类型参数为numbernumber,我们可以将4242传递给identityidentity函数并推断出结果的类型为numbernumber。2.接口泛型接口泛型允许我们创建可适用于不同类型的接口定义。例如:interface Pair first:T;second:U;let pair:Pair=first:42,second:hello;在上面的示例中,我们定义了一个PairPair接口,它接受两个类型参数T T和U U,表示firsfirst t和secondsecond属性的类型。通过指定类型参数为numbernumber和stringstring
41、,我们创建了一个具体的pairpair对象,它的firstfirst属性类型为numbernumber,secondsecond属性类型为stringstring。3.类泛型类泛型允许我们创建可适用于不同类型的类定义。例如:class Container private value:T;五、枚举和泛型www.coding-24constructor(value:T)this.value=value;getValue():T return this.value;letcontainer=newContainer(42);letvalue=container.getValue();/value 的
42、类型为 number在上面的示例中,我们定义了一个ContainerContainer类,它接受一个类型参数T T,表示类的内部值的类型。通过在创建类的实例时显式指定类型参数为numbernumber,我们创建了一个具体的containercontainer对象,它的valuevalue属性类型为numbernumber,并可以使用getValuegetValue方法获取该值。泛型还支持约束(Constraints)的概念,通过使用约束,我们可以限制泛型的类型范围,使其满足特定的条件。泛型在 TypeScript 中广泛应用于函数、类、接口和类型别名的定义中,它提供了一种灵活、类型安全且可重用
43、的方式来处理不同类型的数据。通过使用泛型,我们可以在编写代码时提供更强大的类型支持,从而减少错误并提高代码的可维护性和可读性。六、命名空间和模块www.coding-25六、命名空间和模块1.命名空间(Namespace)在 TypeScript 中,命名空间是一种将代码封装在一个特定名称下的方式,以防止全局作用域污染并避免命名冲突。命名空间在 TypeScript 中非常重要,因为它们为模块化和封装提供了灵活的选项。创建命名空间的语法如下:namespace MyNamespace export const myVar:number=10;export function myFunction
44、():void console.log(Hello from MyNamespace);在此例中,我们创建了一个名为MyNamespaceMyNamespace的命名空间,该命名空间内有一个变量myVarmyVar和一个函数myFunctionmyFunction。exportexport关键字允许我们从命名空间外部访问这些元素。命名空间中的元素可以通过以下方式访问:console.log(MyNamespace.myVar);/输出:10MyNamespace.myFunction();/输出:Hello from MyNamespace我们也可以使用嵌套的命名空间:namespace Pa
45、rentNamespace export namespace ChildNamespace export const myVar:number=20;console.log(ParentNamespace.ChildNamespace.myVar);/输出:20六、命名空间和模块www.coding-262.命名空间(Namespace)使用场景在 TypeScript 的早期版本中,命名空间被广泛地使用来组织和包装一组相关的代码。然而,随着 ES6 模块系统(ES6 Modules)的出现和广泛使用,命名空间的用法变得越来越少,现在被视为一种遗留的方式来组织代码。1)第三方库一些第三方库仍然
46、使用命名空间来组织自己的代码,并提供命名空间作为库的入口点。在这种情况下,我们需要使用命名空间来访问和使用库中的类型和函数。namespace MyLibrary export function myFunction()/.MyLibrary.myFunction();2)兼容性在一些遗留的 JavaScript 代码或库中,命名空间仍然是一种常见的组织代码的方式。如果我们需要与这些代码进行交互,我们可能需要创建命名空间来适应它们。/legacy.jsvar MyNamespace=myFunction:function()/.;MyNamespace.myFunction();在上面的示例中
47、,我们演示了命名空间的几个使用场景。第一个示例展示了如何使用命名空间访问和使用第三方库的函数。第二个示例展示了如何使用命名空间来管理全局状态。第三个示例展示了如何在与遗留 JavaScript 代码进行交互时创建命名空间。六、命名空间和模块www.coding-27虽然在现代 TypeScript 开发中,模块是更常见和推荐的代码组织方式,但命名空间仍然在特定的情况下具有一定的用处,并且在与一些特定的库或代码进行交互时可能是必需的。3.模块在 TypeScript 中,模块是另一种组织代码的方式,但它们更关注的是依赖管理。每个模块都有其自己的作用域,并且只有明确地导出的部分才能在其他模块中访问
48、。创建和使用模块的方式如下:在myModulemyModule.ts 文件中:export const myVar:number=10;export function myFunction():void console.log(Hello from myModule);在另一个文件中导入和使用模块:import myVar,myFunction from./myModule;console.log(myVar);/输出:10myFunction();/输出:Hello from myModule在 TypeScript 中,我们可以使用模块解析策略(如 Node 或 Classic),以确定如
49、何查 找 模 块。这 些 策 略 在tsconfig.jsontsconfig.json文 件 的compilerOptionscompilerOptions选 项 下的moduleResolutionmoduleResolution选项中定义。命名空间与模块的对比虽然命名空间和模块在某种程度上有所相似,但它们有以下几个关键区别:作用域:命名空间是在全局作用域中定义的,而模块则在自己的作用域中定义。这意味着,在模块内部定义的所有内容默认情况下在模块外部是不可见的,除非显式地导出它们。六、命名空间和模块www.coding-28文件组织:命名空间通常用于组织在同一文件中的代码,而模块则是跨文件进
50、行组织。依赖管理:模块关注的是如何导入和导出功能,以便管理代码之间的依赖关系。相比之下,命名空间并未对依赖管理提供明确的支持。使用场景:随着 ES6 模块语法的普及,现代 JavaScript 项目通常更倾向于使用模块来组织代码。然而,对于一些遗留项目或那些需要将多个文件合并为一个全局可用的库的场景,命名空间可能更为合适。七、类型系统层级www.coding-29 进阶七、类型系统层级TypeScript 的类型系统是强类型和静态类型的,这为开发者提供了强大的类型检查和类型安全保障,同时也增加了一定的学习复杂性。为了更好地理解 TypeScript 的类型系统,本文将全面介绍其类型系统层级,包