揭开TypeScript的面纱

2,558 阅读14分钟

整体介绍

TypeScript 是微软公司在 2012 年正式发布。TypeScript 其实就是拥有类型系统的 Javascript 的超集。TypeScript 是建立在 Javascript 之上,最后转换为 Javascript。

TypeScript 的特点:

  • 类型检查。TypeScript 在编译代码时进行严格的静态类型检查。这意味着你可以在编码的阶段发现可能存在的隐患,而不必把它们带到线上。
  • 语言扩展。 TypeScript 会包括 ES6 和未来提案中的特性,比如异步操作和装饰器。也会从其他语言借鉴某些特性,比如接口和抽象类。
  • 工具属性。TypeScript 可以编译成标准的 Javascript。可以在任何浏览器和操作系统上运行。无需任何运行时的额外开销。从这个角度讲TypeScript 更像是一个工具,而不是一门独立的语言。

安装

如果你的本地环境已经安装了 npm 工具,可以使用以下命令来安装:

npm install -g typescript

安装完成后我们可以使用 tsc 命令来执行 TypeScript 的相关代码,以下是查看版本号:

tsc -v
//Version 3.9.7

使用 tsc 命令根据 .ts 文件生成一个 .js 的新文件

tsc app.ts

使用 node 命令来执行 app.js 文件:

node app.js 
Hello World

安装 ts-node

上面通过 tsc 转义.ts文件,再用 node 去执行 js 文件,效率有点低。我们可以借助 ts-node 插件。全局安装 ts-node

npm install -g ts-node

安装完成后,就可以在命令中直接输入如下命令,来查看结果了。

ts-node demo.ts

ts-node 命令直接执行了.ts文件,并且没有生成中间的js文件,方便快捷。

类型

布尔值

let isDone: boolean = false;

TypeScript 中声明变量与 Javascript 语法不同之处在于需要在变量后面 : 类型 来表明变量的类型。

数字

let decLiteral: number = 8;
let num: number = 8.99;

字符串

let name: string = "bob";
name = "smith";
let sentence: string = `Hello, my name is ${name}`

数组

//方法一:
let arr: number[] = [1,2,3];
//方法二:
let list: Array<number> = [1,2,3];

元组

元组表示的是一个已知元素 数量 类型 数组,各元素的类型不必相同。表示方法如下:

let x: [string, number, string, boolean] = ['hello', 10, 'typescript', true];
console.log(Object.prototype.toString.call(x));       // [object Array]

枚举

enum 类型是对 Javascript 标准数据类型的一个补充。使用枚举类型可以为一组数值赋予友好的名字。

enum Color {
	Red,
    Green,
    Blue
};
let c: Color = Color.Red;

默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1开始编号:

enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;

或者,全部都采用手动赋值:

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

console.log(colorName);  // 显示'Green'因为上面代码里它的值是2

any

有时候,我们不希望类型检查器对某些值进行检查而是直接让它们通过编译阶段的检查,这个时候可以使用 any 类型来标记这些变量。这些变量有可能来自用户输入或第三方代码库。

let notSure: any = 4;
notSure.ifItExists();   // okay, ifItExists might exist at runtime
notSure.toFixed();     // okay, toFixed exists (but the compiler doesn't check)

notSure.ifItExists(); 和 notSure.toFixed(); 编译都会通过。

Void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:

function warnUser(): void {
    console.log("This is my warning message");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:

let unusable: void = undefined;

Null 和 Undefined

TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。 和 void相似,它们的本身的类型用处不是很大:

// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;

默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给number类型的变量。

接口

使用 interface 来定义一个接口类型。TypeScript 中的接口可以理解成描述了一种数据类型。比如我们的方法要接收一个参数,这个参数必须有哪些属性或者方法,这个时候就可以使用接口定义好参数的类型。如下:

  interface params{
      search: string;
      page?: number;   //?是可选参数的意思
      size?: number;
  }

  function foo(p: params): string{
      return p.search +"~";
  }
  console.log(foo({search: "中国"}));
  
interface Point{
    readonly x: number;  	//只读属性
    readonly y: number;		//只读属性
}

let point: Point = {x: 10, y: 100};
point.x = 100;			//报错!

接口允许加入任意值增加了接口的灵活性。

  interface params{
      search: string;
      page?: number;   //?是可选参数的意思
      size?: number;
      [propname: string]: any;   //任意多个任意值
  }

  function foo(p: params): string{
      return p.search +"~";
  }
  console.log(foo({search: "中国", test: '11', ee: true, jj: 444, kkk: "eee"}));

类可以实现某个接口:

  interface ShapeConfig{
      type: string;
      color?: string;
      width?: number;
      draw(p: string): string;
  }

  class Circle implements ShapeConfig {
      type = 'circle';
      draw(){  //没有参数不会检查,如果类型不对或者个数不对会显示错误
          return this.type;
      }
  }
  let cricle = new Circle();

接口也可以继承某一个接口:

  interface ShapeConfig{
      type: string;
      color?: string;
      width?: number;
      draw(p: string): string;
  }

  interface Square extends ShapeConfig{
      height: string;
  }

继承 通过 extends 实现继承

class Animal{
    //要先声明name属性
    name: string;
    constructor(theName: string){
        this.name = theName;
    }

    move(distanceMeters: number = 0){
        console.log(`Animnal moved ${distanceMeters}m.`)
    }
}

class Dog extends Animal{
    type: string;
    constructor(name: string, theType: string){
        super(name);
        this.type = theType;
    }
    bark(): void{
        console.log(`I am a ${this.type}, bark bark`);
    }
    move(distanceMeters: number = 10, place: string="zoom"){
        super.move(100);
        console.log(`I want to go ${place}`)
    }
}

let dog = new Dog("Mike", "dog");
dog.bark();
dog.move();
  • Dog 类通过 extends 继承了 Animal 类。Dog 类叫做 Animal 的子类;Animal 叫做 Dog 的超类。

  • 在子类的构造函数中,必须要在访问 this 的属性之前,一定要先调用 super() . 这是 TypeScript强制执行的一条重要规则。(这一点与 ES6 的 class 继承是一致的。)

  • super 的另一个用处就是子类要复用父类中的方法的时候可以先通过 super.xxxx() 去调用父类的方法。

修饰符

class Animal{
    //要先声明name属性
    public name: string;
    protected age: number = 1;
    private id: string = "20201203";
    constructor(theName: string){
        this.name = theName;
    }

    move(distanceMeters: number = 0){
        console.log(`Animnal moved ${distanceMeters}m.`)
    }
}

class Dog extends Animal{
    readonly gender: string;
    constructor(name: string, theGender: string){
        super(name);
        this.gender = theGender;
    }

    move(distanceMeters: number = 10, place: string="zoom"){
        super.move(100);
        console.log(`I am ${this.name}, I am ${this.age} years old. I want to go ${place}`)
    }
}

let dog = new Dog('Mike', "male");
console.log(dog.name);
dog.move();
dog.gender = 'female';   //不能修改,会报错 Cannot assign to 'gender' because it is a read-only property.

Animal 中 name 是 public, 在 Dog 类中以及类定义的外面都能被访问;
Animal 中 age 是 protected, 只能在 Dog 类中被访问;
Animal 中 id 是 private, 只能在 Animal 类中被访问;
Dog 中的 gender 是 readonly, 不能被修改。

参数属性 可以方便地让我们在一个地方定义并初始化一个成员。

  class Shape{
      name: string;
      constructor(public type: string, theName: string){
          this.name = theName;
      }
      greet():void{
          console.log(`I am ${this.name}, I am a ${this.type}`)
      }
  }

  let s = new Shape("circle", "Mike");
  s.greet();

Shape 中 name 和 type 定义是等价的。其中 type 前面的修饰符是不能省略的,必须有修饰符(public、protected、private、readonly)来表明它是成员变量。

存取器 在 ES6 中的 class 中定义的 get 和 set 方法。

    class Counter{
        constructor(){
            this._count = 0;
        }
        get count(){
            console.log("Getting the current value!")
            return this._count;
        }
        set count(value){
            this._count = value;
        }
    }
    let c = new Counter();
    c.count = 100;
    console.log(c.count);

但其实这么设计,我们在 Counter 外面还是能访问和修改 _count 的内容的。在 TypeScript 借助 访问修饰符 private,就可以实现内部成员变量的 访问和修改的拦截啦。

  class Counter{
      private _count: number = 0;
      get count(): number{
          return this._count;
      }
      set count(value: number){
          this._count = value;
      }
  }

  let c = new Counter();
  c.count = 100;

静态属性,同样也是延续的 ES6 中的语法。在 ES6 中,可以使用 static 关键字把属性定义在 class 上。

  //ES6
  class Grid{
      static origin = {x: 0, y: 0};
      constructor(scale){
          this.scale = scale;
      }
      calculateDistanceFromOrigin(point){
          let xDist = (point.x-Grid.origin.x);
          let yDist = (point.y - Grid.origin.y);
          return Math.sqrt(xDist*xDist + yDist*yDist)/this.scale;
      }
  }
  let grid = new Grid(1);
  console.log(grid.calculateDistanceFromOrigin({x: 10, y: 10}));

在 TypeScript 中,我们可以借助 private 修饰符来保证 static 定义的属性不被修改

class Grid{
    private static origin = {x: 0, y:0};
    constructor(public scale: number){}
    calculateDistanceFromOrigin(point: {x: number; y: number}){
        let xDist = (point.x-Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist*xDist + yDist*yDist)/this.scale;
    }
}
let grid1 = new Grid(1);
console.log(grid1.calculateDistanceFromOrigin({x:10, y:10}))

抽象类 跟普通类的区别在于用 abstract 修饰。里面可以包含(不一定非得有)abstract 修饰的抽象方法,该方法只有方法声明,不能包含方法体。抽象类可以被其他类继承,子类必须实现抽象类里面的抽象方法。

抽象类的出现解决的是子类某些方法可能并不需要超类去实现,只需要超类声明,子类必须去实现。

abstract class Animal{
    type: string = "animal";
    abstract makeSound(name: string): void;
    move():void{
        console.log("roaming the earch")
    }
}

class Cat extends Animal{
    makeSound(){
        console.log("miao miao");
    }

}

let cat = new Cat();
cat.move();   //普通方法也会被继承
cat.makeSound();

最后,我们可以把类当作接口使用,接口可以继承类

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

泛型

有的时候我们为了保持类型的一致性,这个时候就需要使用泛型了。泛型保证了类型的非确定性和一致性。
在函数中的使用,我们为了保证函数返回值类型和输入变量类型一致,我们可以使用泛型。

function identity<T>(arg: T): T{
    return arg;
}
//我们在使用的时候最好 identify<number> 来表明泛型的具体类型。
identify<number>(12);

在函数名后面使用 <T> 来表示该函数使用了泛型。参数 arg 的类型是 T,那么返回的类型也必须是 T 这个类型的。

在数组中的使用
前面说到数组定义的两种方法:

//方法一
let arr: number[] = [1,3,4];
// 方法二
let list: Array<string> = ["hello", "world"];

可以加以利用在函数中使用泛型数组

  function myFun<T>(params: T[]): number{
      return params.length;
  }
  myFun<number>([12, '222']); //报错
  myFun([12, '222']);  //不报错

  function myFun2<T>(params: Array<T>): number {
      return params.length;
  }

  myFun2<number>([12, '222']);   //报错
  myFun2([12, '222']);   //不报错

可以看到我们在调用泛型函数的时候最好加上具体的类型,保持类型的一致性。

多个泛型的定义 函数中其实,我们可以定义多个类型。

function join<T,P,H>(first: T, second: P, third: H): H{
    return third;
}
console.log(join<string, number, boolean>("hello", 12, true));

泛型接口 接口中为了保证类型的一致性,我们也可以使用泛型。

//方法一:声明接口为泛型类型的
interface GenericIdentityFn<T>{
    (arg: T): T;
}

function indentify<T>(params: T): T {
    return params;
}
//使用的时候必须加上 <number> 限定具体类型
let test: GenericIdentityFn<number> = indentify;

//方法二:将泛型函数作为接口里面的一个匿名函数
interface GenericIdentityFn{
    <T>(arg: T): T;
}

function indentify<T>(params: T): T {
    return params;
}

let test: GenericIdentityFn = indentify;

泛型类

泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

class Select<T>{
    constructor(private arr: T[]){}
    getItem(index: number): T{
        return this.arr[index];
    }
}

let s = new Select(['hello', 'world']);
console.log(s.getItem(0));

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

泛型中的继承 泛型可以继承自某个接口或者类。继承了某些类型的泛型后其实就有了约束条件了。下面代码中 T 不再是任意类型,而是要有 id 属性的类型才可以。

interface person{
    id: string;
}
class Select<T extends person>{
    constructor(private arr: T[]){}
    getItem(index: number): string{
        return this.arr[index].id;
    }
}

let s = new Select([{id: "Amanda"}, {id: "Willim"}]);
console.log(s.getItem(0));

高级类型

交叉类型(Intersection Types)

交叉类型是将多个类型合并为一个类型,同时兼有取并集。用 & 表示。

interface interface1 {
    name: string;
}
interface interface2 {
    walk(): void;
}
function foo(params: interface1 & interface2) {
    console.log(params.name);
    params.walk();
}
let p = {
    name: "Willim",
    walk(){
        console.log("walk...")
    }
}
// foo需要的参数既有 name 属性又得有 walk 属性。
foo(p);

联合类型(Union Types)

联合类型是 “或者” 的含义,类型1 或者 类型2。比如某个函数的参数可以是 string 类型的或者 number 类型的。用 | 来表示。

function foo(params: number | string){
    console.log(params);
}

foo(13);
foo("hello typescript")

模块和命名空间

模块

TypeScript 中的模块和 ES6 的 module 用法保持一致。使用 export 语法导出模块的变量和方法;使用 import 引入其他模块的变量和方法。

  // math.ts
  export function add(x: number, y: number): number{
      return x+y;
  }
  const PI = 3.14;
  export default PI;

引入:

  // index.ts
  import PI, {add} from './math';
  console.log(add(10, 20));
  console.log(PI);

命名空间

在 Javascript 命名空间能有效避免全局污染( www.cnblogs.com/zyl910/p/js… )。随着 ES6 中 modules 的发展,原始的 JS 命名空间很少被提及了。TypeScript 中依然实现了这个特性。

TypeScript 中使用 namespace 关键字来实现命名空间。在 Shape 命名空间下的变量只有在该命名空间下可以访问,如果要在全局访问的变量或者方法,要通过 export 关键字导出。

随着程序的不断扩大,命名空间的内容可能会逐渐增多。那么命名空间是可以拆分的。如下是 Shape 被拆在了 2个文件中:a.ts 和 b.ts,它们之间是共享一个命名空间的。

// a.ts
namespace Shape{
    const PI = 3.14;
    export function circle(r: number){
        return PI * r * 2;
    }
}
// b.ts
/// <reference path="a.ts"/>
namespace Shape{
    export function square(x: number) {
        return x * x;
    }
}

console.log(Shape.circle(10));
console.log(Shape.square(2));

在 b.ts 中使用了 a 中的 方法 circle,所以需要先通过 /// 引入 a.ts 文件。分别编译 a.ts 和 b.ts,生成对应的文件 a.js 和 b.js 。下面是对应编译的文件:

// a.js
var Shape;
(function (Shape) {
    var PI = 3.14;
    function circle(r) {
        return PI * r * 2;
    }
    Shape.circle = circle;
})(Shape || (Shape = {}));

// b.js
/// <reference path="a.ts"/>
var Shape;
(function (Shape) {
    function square(x) {
        return x * x;
    }
    Shape.square = square;
})(Shape || (Shape = {}));
console.log(Shape.circle(10));
console.log(Shape.square(2));

可以看到 namespace 被编译成了一个全局变量和立即执行函数。在 index.html 中引入 a.js 和 b.js 文件,可以看到执行环境。并且我们还可以在浏览器的 console 命令台中,输入 Shape, 看到这个全局变量。
命名空间的别名使用:可以使用 import XX = Shape.circle; 来简化代码。这里的 import 跟 ES6 种的 import 没有关系的,不要混淆。

// b.ts
import circle = Shape.circle;
console.log(circle(100));

在早期 TypeScript 中命名空间也叫内部模块,可以起到隔离作用域的作用。随着 ES6 modules 的发展,内部模块已经不再叫了。TypeScript 中保留了命名空间其实是对全局时代的一种兼容。

最后, 命名空间不要和模块混用 ,同时命名空间的使用最好在一个全局的环境中使用

声明合并

声明合并是指 TypeScript 会把多个声明合并为一个声明。这样做的好处就是把程序中散落各处的声明合并在一起。

接口的声明合并

interface A{
    x: number;
}

interface A{
    y: number;
}

编译器会合并成如下:

interface A{
    x: number;
    y: number;
}

对于接口非函数的成员,如果定义重复了,类型必须一致,否则会报错

interface A{
    x: number;
    y: string;
}

interface A{
    y: number;  //报错
}

对于接口的函数成员,如果重复定义了会出现函数的重载。首先补充一下 TypeScript 中函数重载

function add5(params: number[], name: string): number;
function add5(params: string[]): string;
function add5(params: any, name?: string): any{
    if(typeof params[0] === "number"){
        console.log(name);
        return params.reduce((pre, cur) => pre+cur);
    }
    if(typeof params[0] === "string"){
        return params.join("")
    }
}

console.log(add5([1,2,3,4], "add number function"));
console.log(add5(["1", "2", "3", "4"]));

我们要能理解函数重载存在的意义。如果没有函数重载,直接去声明和实现 add5 也是可以的。但是这样并不能保证用户按照我们想要的参数格式和个数进行调用。提前声明 add5 参数的所有可能,再利用函数的重载分别进行实现。比如 add5 想调用数字数组的时候必须要传第二参数,保证程序按照我们代码的逻辑去执行。 函数重载的作用在于把函数接收参数的情况提前声明好,便于 TypeScript 进行参数类型检查。

接口中声明的函数合并情况如下:

  • 每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。
  • 这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是 单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。
  interface Cloner {
      clone(animal: Animal): Animal;
  }

  interface Cloner {
      clone(animal: Sheep): Sheep;
  }

  interface Cloner {
      clone(animal: Dog): Dog;
      clone(animal: Cat): Cat;
  }
  // 每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。
  //合并后:
  interface Cloner {
      clone(animal: Dog): Dog;
      clone(animal: Cat): Cat;
      clone(animal: Sheep): Sheep;
      clone(animal: Animal): Animal;
  }

含有单一的字符串字面量的情况:

interface Document {
    createElement(tagName: any): Element;
}
interface Document {
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
    createElement(tagName: string): HTMLElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
}
//单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。
//合并后:
interface Document {
    createElement(tagName: "canvas"): HTMLCanvasElement;
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: string): HTMLElement;
    createElement(tagName: any): Element;
}

命名空间的声明合并

  • 两个命名空间中 export 导出的成员是不能重复定义的。
// a.ts
namespace Shape{
    const PI = 3.14;
    export function circle(r: number){
        return PI * r * 2;
    }
}
// b.ts
namespace Shape{
    const PI = 3.14;
    export function square(x: number) {
        return x * x;
    }
    //报错,不能重复声明
    export function circle(r: number){
        return PI * r * 2;
    }
}
  • 命名空间和函数的声明合并
  function Lib(){}
  namespace Lib{
      export const version = "1.0.0";
  }
  // 命名空间和函数的声明合并相当于给函数定义了某些属性。
  console.log(Lib.version);
  • 命名空间和类的声明合并
class C{}
namespace C{
    export const state = 1;
}
// 命名空间和类的声明合并相当于给类定义了某些静态属性
console.log(C.state);
  • 命名空间和枚举的声明合并
enum Color{
    Red,
    Black
}

namespace Color{
    export function mixin() {
        
    }
}
// 枚举类型和命名空间的声明合并相当于给枚举定义了一个方法
Color.mixin();

在命名空间与类、函数进行声明合并的时候,一定要将命名空间放在类、函数之后。否则报错。枚举没有这个问题。

声明文件

如果我们想在 TypeScript 中使用第三方类库如 jQuery、lodash 等,需要提供这些类库的声明文件(以.d.ts 结尾),对外暴漏 API。 一般我们通过安装第三方类库的类型声明包后,即可在 TypeScript 中使用。以 jQuery 为例:

npm install -D jquery @types/jquery

安装后,我们就可以在 TypeScript 中使用 jQuery 的方法了。

// index.ts
import $ from 'jquery';
$(".app").css("color", "red");

如果是我们自己封装的类库,我们也可以自己编写对应的声明文件(.d.ts)。

全局类库

  // 全局类库 global-lib.js
  function globalLib(options) {
     console.log(options);
  }
  globalLib.version = "1.0.0";
  globalLib.doSomething = function () {
     console.log('globalLib do something');
  };
  1. 因为是全局类库,可以直接在 html 文件中以 script 标签引入原始 js 文件 global-lib.js。
  2. 将写好的声明文件与js库放在同一文件夹下,名字相同,后缀名为.d.ts (global-lib.d.ts)。此时可以在ts文件中使用全局API。
  3. 编写 global-lib.d.ts 文件。
declare function globalLib(options: globalLib.Options): void;
declare namespace globalLib{
   const version: string;
   function doSomething(): void;
   interface Options {
      [key: string]: any,
   }
}
  1. 至此,我们可以在 index.ts 文件中使用 global-lib.js 里面的方法了。如:globalLib.doSomething();

模块类库

  // 模块类库  module-lib.js
  const version = "1.0.0";
  function doSomething() {
     console.log('moduleLib do something');
  }
  function moduleLib(options) {
     console.log(options);
  }
  moduleLib.version = version;
  moduleLib.doSomething = doSomething;
  module.exports = moduleLib;
  1. 将声明文件放在相同的目录下 (module-lib.d.ts)
  2. 编写声明文件
//module-lib.d.ts
  declare function moduleLib(options: Options): void;
  interface Options {
     [key: string]: any,
  }
  declare namespace moduleLib{
     const version: string;
     function doSomething(): void;
  }
  export = moduleLib; // 这样写兼容性更好
  1. 在ts中使用
// 需要先导入
import moduleLib from './lib/module-lib.js';
moduleLib.doSomething();

UMD类库

UMD库有两种使用方式:

  • 引入全局类库的方式(同全局类库的方式一样)
  • 模块类库引入的方式
//umd-lib.js
(function (root, factory) {
    if(typeof define === "function" && define.amd)
    {
       define(factory);
    }else if(typeof module === "object" && module.exports)
    {
       module.exports = factory();
    }else
    {
       root.umdLib = factory();
    }
 })(this, function () {
    return {
       version: "1.0.2",
       doSomething() {
          console.log('umdLib do something');
       }
    }
 });

UMD库模块引入的方式:

  1. 编写相应的 d.ts 文件。
//umd-lib.d.ts
declare namespace umdLib {
    const version: string;
    function doSomething(): void;
 }
 export as namespace umdLib // 专门为umd库准备的语句,不可缺少
 export = umdLib
  1. ts中使用UMD库
import umdLib from './lib/umd-lib'
umdLib.doSomething();
console.log(umdLib.version);

为类库添加插件

即为类库添加自定义的方法,其中UMD库和模块类库的添加插件方法一致。

// 为全局类库增添自定义方法
declare global {
   namespace globalLib {
      function myFunction(): void
   }
}
globalLib.myFunction = () =>{console.log("global插件")};
 
// 为模块类库添加自定义方法
declare module "./lib/module-lib.js"{
   export function myFunction(): void;
} // 为module-lib类库声明myFunction方法
moduleLib.myFunction = () => {console.log("module插件")}; // 定义自定义方法
 
// 为UMD库添加自定义方法
declare module "./lib/umd-lib.js"{
   export function myFunction(): void;
} // 为umd-lib类库声明myFunction方法
umdLib.myFunction = () => {console.log("umd插件")}; // 定义自定义方法
 
globalLib.myFunction();
moduleLib.myFunction();
umdLib.myFunction();

例如,为类库moment增添自定义方法(jQuery不可以,需要使用官方提供的API)

npm install -D moment @types/moment
 
import m from 'moment';
declare module 'moment'{
   export function myFunction(): void;
}
m.myFunction = () => {console.log("moment插件")};
 
m.myFunction();

参考:www.cnblogs.com/V587Chinese…

工程文件git地址:github.com/YY88Xu/ts-i…

感谢

如果本文有帮助到你的地方,记得点赞哦,这将是我持续不断创作的动力~