Nest是基于TypeScript进行构建的,它提供了类型安全系统,并引入了面向对象编程、装饰器和元数据等概念。这些概念贯穿于Nest的源码设计和项目实践开发中。本节将从TypeScript的编译配置和类型系统两大板块着手,回顾TypeScript的基础知识。
TypeScript被设计为JavaScript的超集,在使用时需要通过特定的编译器将其编译为普通的JavaScript才能被浏览器识别。编译器运行的环境被称为TypeScript的编译上下文,它包含编译器需要用到的各种配置选项、输入输出文件等信息。而tsconfig.json文件就是用来指定编译器运行时的行为,即编译选项。
通常用compilerOptions来定制编译选项,它包含丰富的选项,示例如下:
TypeScript内置了TSC编译器,可以通过以下方式运行它:
● 运行tsc,它会在当前目录或父级目录中寻找tsconfig.json文件。
● 运行tsc -p ./path-to-project-directory。这里的路径可以是绝对路径,也可以是相对于当前目录的相对路径。
可以用include和exclude选项来指定需要包含的文件和排除的文件:
类型注解使用“:TypeAnnotation”语法。在类型声明空间中,可用的任何内容都可以用作类型注解。
在下面这个例子中,使用了变量、函数参数以及函数返回值的类型注解:
const num: number = 123; function identity(num: number): number { return num; }
JavaScript的原始类型同样适用于TypeScript的类型系统,因此string、number、boolean也可以用作类型注解:
let num: number; let str: string; let bool: boolean; num = 123; num = 123.456; num = '123'; // Error
TypeScript为数组提供了专用的类型语法,因此我们可以很容易地为数组添加类型注解,代码如下:
let boolArray: boolean[]; boolArray = [true, false]; console.log(boolArray[0]); // true
接口可以将多个类型声明合并为一个类型声明:
interface Name { first: string; second: string; } let name: Name; name = { first: 'John', second: 'Doe' }; name = { // Error: 'Second is missing' first: 'John' };
在这里,我们把类型注解first: string + last: string合并到了一个新的类型注解Name中,这样能够强制对每个成员进行类型检查。
除了前面提到的一些原始类型,TypeScript中还存在一些特殊的类型,它们是any、null、undefined以及void。
1)any
any类型为我们提供了一个类型系统的“后门”,使用它时,TypeScript会关闭类型检查。在类型系统中,any能够兼容所有的类型(包括它自己),代码如下:
let power: any; // 赋值任意类型 power = '123'; power = 123;
2)null和undefined
在类型系统中,JavaScript中的null和undefined字面量与其他被标注了any类型的变量一样,都可以赋值给任意类型的变量,但不会关闭类型检查,代码如下:
// strictNullChecks: false let num: number; let str: string; // 这些类型可用于赋值 num = null; str = undefined;
3)void
void表示没有任何类型,通常用于那些不返回任何值的函数的返回类型。
function myFunction(): void { // 函数体 }
泛型可以应用于Typescript中的函数(函数参数、函数返回值)、接口和类(类的实例成员、类的方法)。
下面我们来创建第一个使用泛型的示例函数:reverse()。这个函数会返回传入的任意值。在不使用泛型的情况下,这个函数可能是这样的:
function reverse(arg: any): any { return arg; };
使用any类型会导致这个函数可以接收任何类型的arg参数,但我们希望传入的类型与返回的类型相同。
因此,需要一种方法来确保返回值的类型与传入参数的类型相同。这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值,代码如下:
function reverse<T>(arg: T): T { return arg; }
我们给reverse添加了类型变量T。T帮助我们捕获用户传入的类型(比如string),之后把T用作返回值类型,这样就可以跟踪函数中使用的类型信息了。
我们把这个版本的reverse()函数称为泛型,因为它适用于多个类型。
我们希望JavaScript属性能够支持多种类型,例如字符串或者字符串数组。在TypeScript中,可以使用联合类型来满足这一需求(联合类型使用“|”作为分隔符,例如TypeA | TypeB)。以下是关于联合类型的例子:
在JavaScript中,extend是一种非常常见的模式。在这种模式下,我们可以通过两个对象创建一个新对象,使新对象继承这两个对象的所有功能。交叉类型可以让我们安全地使用这种模式:
function extend<T extends object, U extends object>(first: T, second: U): T & U { const result = {} as T & U; for (let id in first) { (result as T)[id] = first[id]; } for (let id in second) { (result as U)[id] = second[id]; } return result; }
TypeScript提供了一种便捷的语法来为类型注解设置别名。我们可以使用type SomeName =someValidTypeAnnotation来创建别名:
type StrOrNum = string | number; // 使用 let sample: StrOrNum; sample = 123; sample = '123'; // 检查类型 sample = true; // Error
TypeScript允许覆盖其类型推断,并可以按照我们期望的方式重新分析类型。这种机制被称为“类型断言”。类型断言用于告知编译器,我们对该类型的了解比编译器的推断更为准确,因此编译器不应再发出类型错误。以下是类型断言的常见用法示例:
这里的代码发出了错误警告,因为auth的类型为number|number[],其属性没有some()方法。因此,我们不能使用some()方法。虽然已经知道auth的实际类型是number[],但TypeScript无法推导出这一点。在这种情况下,可以通过类型断言来避免此问题:
枚举是一种用于收集相关值的集合的方法,示例如下:
enum Status { Success, Error, Warning } enum Env { Null = '', Dev = 'development', Prod = 'production' } // 简单使用 let status = Status.Success; let env = Env.Dev;
TypeScript允许我们声明函数重载。这对于文档+类型安全非常实用。请看以下代码:
我们可以通过函数重载来强制执行并记录这些约束,只需多次声明函数头即可。最后一个函数头在函数体内实际处于活动状态,但这函数头不可在外部直接使用,如下所示:
本节通过示例介绍了TypeScript的基本概念和用法,这些类型在Nest开发中都会频繁使用。掌握本节内容可以为我们以后编写Nest应用打下坚实的基础。