类型断言

可以用来手动指定一个值的类型。
有两种方式:

  1. <类型>值
  2. 值 as 类型(在tsx语法中必须用这一种)

例子:

  1. let someValue: any = "this is a string";
  2. let strLength: number = (<string>someValue).length;

经常用在将一个联合类型的变量指定为一个更加具体的类型。有时候,我们确实需要在还不确定类型的时候就访问一个类型的属性或方法。
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

交叉类型

将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。例如: Firest & Second 同时是 First 和 Second 。就是说这个类型的对象同时拥有了这两种类型的成员。

联合类型

表示一个值可以是几种类型之一。我们用竖线(|)分隔每个类型,所以 number | string | boolean 表示一个值可以是 number , string , boolean。
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。

类型守卫

类型守卫就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型(也就是一旦检查过类型,就能在之后的每个分支里清楚地知道这个类型)。

有下面几种方法:

typeof

typeof 类型守卫只有两种形式能被是被: typeof v === 'typename'typeof v !== 'typename'typename 必须是 number , string , boolean , symbol

instanceof

instanceof 类型守卫是通过构造函数来细化类型的一种方式。 instanceof 的右侧要求是一个构造函数。

  1. class Person {
  2. name: "xiaomuzhu";
  3. age = 20;
  4. }
  5. class Animal {
  6. name = "petty";
  7. color = "pink";
  8. }
  9. function getSomething(arg: Person | Animal) {
  10. if (arg instanceof Animal) {
  11. console.log(arg.color);
  12. console.log(arg.age); // Error
  13. }
  14. if (arg instanceof Person) {
  15. console.log(arg.age);
  16. console.log(arg.color); // Error
  17. }
  18. }

in

in 操作符可以作为类型细化表达式来使用。

  1. function move(pet: Fish | Bird) {
  2. if ("swim" in pet) {
  3. return pet.swim();
  4. }
  5. return pet.fly();
  6. }

字面量类型守卫

  1. e Foo = {
  2. kind: "foo";
  3. foo: number;
  4. };
  5. type Bar = {
  6. kind: "bar";
  7. bar: number;
  8. };
  9. function doStuff(arg: Foo | Bar) {
  10. if (arg.kind === "foo") {
  11. console.log(arg.foo);
  12. console.log(arg.bar); // error
  13. } else {
  14. console.log(arg.foo); // error
  15. console.log(arg.bar);
  16. }
  17. }

声明合并

如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型。

函数的合并:可以使用重载定义多个函数类型
接口的合并:接口中的属性在合并时会简单的合并到一个接口中。(注意:合并的属性的类型必须是唯一的)接口中方法的合并,与函数的合并一样。
类的合并:类的合并与接口的合并规则一致。

注释的妙用

可以通过 /** */ 来注释 TypeScript 的类型,当我们在使用相关类型的时候就会有注释的提示,这个技巧在多人协作开发的时候十分有用,我们绝大部分情况下不用去花时间翻文档或者跳页去看注释。
image.png

装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
目前装饰器本质上是一个函数, @expression 的形式其实是一个语法糖, expression 求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。

  1. // 普通装饰器
  2. function logClass(params: any) {
  3. console.log(params);
  4. // params 就是当前类
  5. params.prototype.apiUrl = 'xxx';
  6. }
  7. // 注意这里不用写分号
  8. @logClass
  9. class HttpClient {
  10. constructor() {}
  11. getData() {}
  12. }

装饰器工厂

如果我们要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。

  1. function color(value: string) { // 这是一个装饰器工厂
  2. return function(target: any) { // 这是装饰器
  3. // ...
  4. }
  5. }

类装饰器

类装饰器在类声明之前被声明(紧接着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。其参数是类的构造函数。

用装饰器重载类

  1. function logClass(target: any) {
  2. return class extends target {
  3. apiUrl: any = '修改后'
  4. getData() {}
  5. }
  6. }
  7. @logClass
  8. class HttpClient {
  9. public apiUrl: string | undefined;
  10. constructor() {
  11. this.apiUrl = 'aa';
  12. }
  13. getData() {
  14. console.log(this.apiUrl);
  15. }
  16. }
  17. var http = new HttpClient();
  18. http.getData();

属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字

方法装饰器

方法装饰器会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰器会在运行时传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字(要修改的方法名)
  3. 成员的属性描述符

方法参数装饰器

参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一个新的属性,属性中包含一系列信息,这些新奇就被称为元数据,然后我们就可以使用另外一个装饰器来读取元数据。传入下列3个参数:

  1. target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 方法名称
  3. index:参数在函数参数列表中的索引

各种装饰器的执行顺序

  1. 属性装饰器
  2. 方法参数装饰器
  3. 方法装饰器
  4. 类装饰器
  5. 如果有两个相同类型的装饰器,先执行写在后面的那一个装饰器。

模块和命名空间

模块

模块是自声明的,两个模块之间的关系是通过在文件级别上 import 和 export 建立的。简单来说,只要你在文件中使用了 import 和 export 语法,就可以将其视为一个模块。 typescript 官方文档如是说: typescript 和 es6 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。其实,也不一定是顶级。

namespace

在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。如果想在命名空间外访问,则用 export 导出。用关键字 namespace 来声明命名空间。

命名空间的引用

通常情况下,声明的命名空间代码和调用的代码不在同一个文件里。

  1. //biology.ts
  2. namespace Biology {
  3. export interface Animal {
  4. name: string;
  5. eat(): void;
  6. }
  7. export class Dog implements Animal {
  8. name: string;
  9. constructor(theName: string) {
  10. this.name = theName;
  11. }
  12. eat() {
  13. console.log(`${this.name} 吃狗粮。`};
  14. }
  15. }
  1. // app.ts
  2. /// <reference path="biology.ts" />
  3. let dog: Biology.Animal;
  4. dog = new Biology.Dog('狗狗');
  5. dog.eat();

通过 reference 注释引用命名空间,即可通过“完全限定名”进行访问。我们也可以通过 import 导入模块的形式,引入命名空间。

  1. import '.biology'
  2. let dog: Biology.Animal;
  3. dog = new Biology.Dog('狗狗');
  4. dog.eat();

别名

简化命名空间的操作方法:import q = x.y.z 给常用的对象起一个短的名字。但不要和加载模块 import x = require("name") 的语法弄混了,这里的语法只是为指定的符号创建一个别名而已。

例子:

  1. namespace Shapes {
  2. export namespace Polygons {
  3. export class Triangle {}
  4. export class Square {}
  5. }
  6. }
  7. import polygons = Shapes.Polygons;
  8. let sq = new polygons.Square();