2

TS-入门学习笔记

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持。
与js相比,最大的有点是类型系统的引入,由于js本身是弱类型语言,所以天然不存在类型系统,这一方面使得js使用起来更简单,但同时也带来了一定的问题,所以ts重新引入了类型系统,带了了以下的优点:

  • 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
  • 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
  • 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等
ts的文档比较多
中文官网:https://www.tslang.cn/docs/ho...
官方还提供了一些例子:https://github.com/Microsoft/...
入门教程:https://ts.xcatliu.com/basics...
深入理解:https://jkchao.github.io/type...

类型系统

TS最重要的类型系统,除了囊括了最基本的基础类型外,更引入了接口,类,泛型等
更多高级类型见:https://www.tslang.cn/docs/ha...

基本类型

基本类型的变量申明和js没有太大区别,就是增加了类型的申明

let isDone: boolean = false; // 布尔型
let decLiteral: number = 6; // 浮点数
let name: string = "bob"; // 字符串
let sentence: string = `Hello, my name is ${ name }.` // 模板字符串
let unusable: void = undefined; // void表示空,可以是undefined 或者 null
let u: undefined = undefined; //undefined 类型的变量只能被赋值为 undefined
let n: null = null; //null 类型的变量只能被赋值为 null

需要注意的,声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null,与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量

// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;

数组

数组类型有多种定义方式

let list: number[] = [1, 2, 3]; //「类型 + 方括号」
let list: Array<number> = [1, 2, 3]; //数组泛型 Array<elemType>

// 接口形式
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

let list:(number | string)[] =  [1, 2, 3,'4'];  //联合类型的数组
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }]; // 允许数组中出现任何类型的any,不过这种数组原则上不算标准的数组。

需要注意,类数组不是数组类型,比如 arguments,NodeList等,所以无法使用数组类型来声明

任意值

任意值(Any)用来表示允许赋值为任意类型。类型系统的存在就是为了让变量在声明初就决定好自己的类型,如果是一个普通类型,在赋值过程中改变类型是不被允许的,但是 any 类型,则允许被赋值为任意类型(和js很像)

let aAnyNumber: any = '哈哈哈哈';
aAnyNumber = 2333;

类型推论

let x = 3; 
//等价于
let x: number = 3;

//当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。例如,

let x = [0, 1, null];
//等价于
let x:number[] = [0, 1, null];
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种(类型的或概念),用|来划分

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number {
    return something.length;
}
//length 不是 string 和 number 的共有属性,所以会报错。

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错
//被赋值为string的 myFavoriteNumber类型 被推断成了 string,访问它的 length 属性不会报错。
//而重新被赋值为7的 myFavoriteNumber 的类型被推断成了 number,访问它的 length 属性时就报错了。

接口(interface)

与java的接口类似,它是对行为的抽象,声明一种类型的数据,而具体如何行动需要由类(classes)去实现。
TS中接口的作用就是为这些行为的抽象类型命名和为你的代码或第三方代码定义契约

interface IPerson {
    name: string;
    age: number;
}

let tom: IPerson = {
    name: 'Tom',
    age: 25
};
接口一般首字母大写,建议接口前可以加上I

正常情况声明的接口的里的变量都是必填,赋值的时候,变量的形状必须和接口的形状保持一致,不能多也不能少

interface IPerson {
    name: string;
    age: number;
}

let tom: IPerson = {
    name: 'Tom'
};
//error
let tom: IPerson = {
    name: 'Tom',
    age: 25,
      sex:'male'
};
//error

可选属性

当然,有时候接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。这时候可以使用可选属性

interface IPerson {
    name: string;
    age?: number;  //使用问号声明可选属性,可选属性的含义是该属性可以不存在。
}

let tom: IPerson = {
    name: 'Tom'
};

任意属性

当我们能够确定这个对象还会带有任意数量的其它属性时,可以定义额外的属性

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface ISquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}
//SquareConfig可以有任意数量的属性,并且只要它们不是color和width,那么就无所谓它们的类型是什么。

interface IPerson {
    name: string;
    age?: number;
    [propName: string]: string;
}
//实际上上面一段代码会报错,类型“number”的属性“age”不能赋给字符串索引类型“string”。

只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。 可以在属性名前用 readonly来指定只读属性

interface IPerson {
    readonly id: number;
    name: string;
    age?: number;
}

let tom: IPerson = {
    id: 89757,
    name: 'Tom'
};

方法继承接口

我们可以使用接口的方式来定义一个函数需要符合的形状:

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

类实现接口

大多数情况下,我们会把一些类中的公共方法抽象成一个接口,然后由类来实现这个接口

interface Alarm {
    alert();
}
interface Light {
    lightOn();
    lightOff();
}
class Car implements Alarm, Light { //一个类可以实现多个接口
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

接口与接口之间可以是继承关系:

interface Alarm {
    alert();
}
interface LightableAlarm extends Alarm {
    lightOn();
    lightOff();
}

函数

Ts中的函数和js相比,增加了入参的类型校验和返回结果的类型校验。

//一个函数表达式
let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };
//在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
let myAdd = function(x: number, y: number): number { return x + y; };
//实际史昂,上面的函数表达式也是没有问题的,函数的输入输出类型都声明了,左边的函数变量会根据类型推断自动添加类型
//一个函数声明
function sum(x: number, y: number): number {
    return x + y;
}

可选参数和默认参数

和接口定义类似,输入多余的(或者少于要求的)参数,是不允许的,但是同样可以使用可选属性
与接口中的可选属性类似,我们用 ? 表示可选的参数:

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了

默认参数的能力是ES6提供的,在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数。

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
//此时就不受「可选参数必须接在必需参数后面」的限制了

剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数)

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

重载

学过Java或者C++的同学肯定知道函数重载,原本的js由于没有类型系统,自然也就没有严格意义上的函数重载(虽然能通过传入不同的参数而返回不同类型的数据来模拟重载),TS中引入了类型,虽然可以实现类似java的函数重载,但是由于最终要转化成js,所以仍然无法实现真正的函数重载,只能在函数体内自己判断参数的个数和类型,然后实现不同的函数内容。

function reverse(x: number): 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('');
    }
}
//如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型,上面的函数的重载就用了这种方式
关于TS的函数重载可以看知乎的这篇讨论:https://www.zhihu.com/questio...

类(class)

类,定义了一件事物的抽象特点,包含它的属性和方法

类的用法来源于ES6的class,可以参考 ES6-class的基本语法。
这里讲一下TS对类的补充。

访问修饰符

ES6中的类是没有访问修饰符的,TypeScript则 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

同大多数的面向对象语言一样的用法,需要注意的是,由于js的天然原因,private和protected在最终编译出的js中
其实是没有效果的,也就是虽然TS的编译过程中会报错,但是如果你无视掉这个报错编译出来的js代码其实是可以正常跑的

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';

// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

抽象类

这块类的如果有java或者c++基础,应该会很好理解,抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。
不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法

1.抽象类是不允许被实例化的
2.抽象类中的抽象方法不包含具体实现并且必须在派生类中实现

抽象类和接口有点类似,但是还是有不少区别的

1.抽象类要被子类继承,接口要被子类实现.
2.接口里面只能对方法进行声明,抽象类既可以对方法进行声明也可以对方法进行实现(抽象类中可以有非抽象的方法)
3.抽象类说到底还是类,拥有类的一切功能(构造函数,访问修饰符等),而接口方法默认修饰符是public。你不可以使用其它修饰符
abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi();
}

class Cat extends Animal {
    public sayHi() {
        console.log(`Meow, My name is ${this.name}`);
    }
}

let cat = new Cat('Tom');

完整的类

class Animal {
      name = 'Jack'; //默认都为公有
      public publicName = 'publicJack'; //公有属性可以在任何地方被访问到
      private privateName = 'privateJack';// 私有属性不能在声明它的类的外部访问
      readonly numberOfLegs: number = 8; //使用 readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化
    public constructor(name) {
        this.name = name; //构造函数
    }
      static isAnimal(a) { //使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用
        return a instanceof Animal;
    }
    get name() {
        return 'Jack';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true,直接调用静态方法
console.log(a.sayHi()); // My name is Jack
console.log(a.privateName); //error,Property 'name' is private and only accessible within class 'Animal'
a.name = 'Tom'; // setter: Tom
console.log(a.sayHi()); // My name is Tom
// Cat类继承于Animal
class Cat extends Animal {
    constructor(name) {
        super(name); // 子类中使用 super 关键字来调用父类的构造函数和方法,调用父类的 constructor(name) 
        console.log(this.name);
    }
    sayHi() {
        return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
    }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

泛型是一个比较复杂和难以理解的东西,详细的可以参考https://www.tslang.cn/docs/ha...

function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
//函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。
createArray<string>(3, 'x'); // ['x', 'x', 'x']
//接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来:

//定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);
    return arg;
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

上例中,泛型 T 不一定包含属性 length,所以编译的时候报错了。
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束.
之前也说了,接口可以用来描述约束条件,所以我们可以用接口来限制泛型的类型。

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

上例中,我们使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。
此时如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了:

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
loggingIdentity(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.

泛型接口和泛型类

//例子1
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

//例子2
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

//例子3
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

例子1中,我们在泛型函数中声明了一个T的泛型类型,在例子2中我们吧这个对象字面量抽出来成为一个接口,例子3中,我们更进一步,把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。我们不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。 当我们使用GenericIdentityFn的时候,还得传入一个类型参数来指定泛型类型(这里是:number)。

与泛型接口类似,泛型也可以用于类的类型定义中,使用方法也基本一致。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
//枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
console.log(Direction['Up']) //0
console.log(Direction['Down']) //1
console.log(Direction[0]) //Up
console.log(Direction[1]) //Down

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
//当然也可以自己手动赋值,未手动赋值的枚举项会接着上一个枚举项递增
console.log(Direction['Up']) //1
console.log(Direction['Down']) //2

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
//字符串枚举也是可以的

常量枚举

为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义

常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员
const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

//上例的编译结果是,枚举值会在编译阶段直接替换

var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

外部枚举

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

//编译结果
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

外部枚举与声明语句一样,常出现在声明文件中。

字面量类型和类型别名

类型别名用来给一个类型起个新名字
字符串字面量类型用来约束取值只能是某几个字符串中的一个。

类型别名与字符串字面量类型都是使用 type 进行定义

type Name = string; //使用type将string的别名为Name
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}
type EventNames = 'click' | 'scroll' | 'mousemove'; //使用type声明字面量类型
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'

模块

TS的模块机制与ES6的模块机制基本保持一致(这里的模块我们指“外部模块”,内部模块则成为上面所讲的命名空间),
一般以一个独立的文件为一个模块,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有
顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)

模块和命名空间类似,模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等
在模块外部是不可见的,除非你明确地使用export导出它们。

TS的模块导入导出,可以参考(ES6的模块机制)[https://es6.ruanyifeng.com/#d...],通过export和import来实现导入导出。

导出

使用export支持导出任何声明(比如变量,函数,类,类型别名或接口)

export interface StringValidator {
    isAcceptable(s: string): boolean;
}
export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export { ZipCodeValidator as mainValidator };//对导出的部分重命名
declare let $: JQuery;
export default $; // 和ES6一样的默认导出

导入

通过import导入模块

import { ZipCodeValidator } from "./ZipCodeValidator";

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; //可以对导入内容重命名

import * as validator from "./ZipCodeValidator"; //将整个模块导入到一个变量,并通过它来访问模块的导出部分

import $ from "./Jquery"; //导出模块的默认导出

CommonJS支持

由于ES6语法并没有在浏览器端被完全支持,所以我们常用的模块加载方式还是commonjs,
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。export =语法
定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举。

若使用export =导出一个模块,则必须使用TypeScript的特定语法import
module = require("module")来导入此模块(ES6模块和Commonjs的混合??)。

//导出
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;


//导入
import zip = require("./ZipCodeValidator");

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validator = new zip();

// Show whether each string passed each validator
strings.forEach(s => {
  console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});

如何创建模块

以下原则和TS没有太大关系,无论是js还是Ts都应遵循以下原则:

1.尽可能地在顶层导出
2.如果仅导出单个 class 或 function,使用 export default当然也有习惯完全不使用的export default的也没有问题
3.如果要导出多个对象,把它们放在顶层里导出,类似下面
export class SomeType { / ... / }
export function someFunc() { / ... / }
4.明确地列出导入的名字
import { SomeType, someFunc } from "./MyThings";
5.使用命名空间导入模式当你要导出大量内容的时候
import * as myLargeModule from "./MyLargeModule.ts";
6.使用重新导出进行扩展
7.模块里不要使用命名空间

声明文件

什么是声明文件,简单的说声明文件就是用来声明你这个文件或者库里有哪些变量,哪些类,哪些函数和接口等等。

当我们需要使用到第三方库时,我们需要引用它的声明文件,这样TS才能知道这些库有哪些接口和类,
才能获得对应的代码补全、接口提示等功能

声明文件必需以 .d.ts 为后缀,里面包含了这个库相关的声明语句

引用声明文件

在我们的项目开发中,如果要引用第三方库的话,一般还是需要引用它的声明文件,我们可以直接从社区或者其他渠道
找到对应的声明文件,直接下载下来使用,当然更推荐的是使用 @types 统一管理第三方库的声明文件。
比如,获取lodash库的声明文件,只需使用下面的命令:

npm install --save @types/lodash

大多数情况下,类型声明包的名字总是与它们在npm上的包的名字相同,但是有@types/前缀, 但如果你需要的
话,你可以在 https://aka.ms/types这里查找...
当然,如果一个npm包发布的时候已经包含了它的声明文件,那就不必再去下载相应的@types包了(判断依据是
package.json 中有 types 字段,或者有一个 index.d.ts声明文件)

如果一个包本身既不包含声明文件,有没有再@types中发布声明文件,那么我们就需要自己写声明文件了,推荐可
以创建一个 types 目录,专门用来管理自己写的声明文件。这种方式需要配置下 tsconfig.json 的 paths 和 baseUrl 字段。


{
    "compilerOptions": {
        "module": "commonjs",
        "baseUrl": "./",
        "paths": {
            "*" : ["types/*"]
        }
    }
}

书写声明文件

当一个第三方库没有提供声明文件或者我们要发布我们自己的包时,我们就需要自己书写声明文件了。

声明文件,有声明语句组成,一般用declare来标示声明,对于变量,只要描述其类型,对于函数,需要描述其变量和返回

声明文件建议可以把变量,类型和函数分开为三个文件

变量

使用declare var声明变量。 如果变量是只读的,那么可以使用 declare const。 你还可以使用 declare let如果变量拥有块级作用域。

/** 组件总数 */ 
declare var foo: number;
函数

使用declare function 用来定义全局函数的类型

declare function greet(greeting: string): void;

使用declare class描述一个类或像类一样的对象。 类可以有属性和方法,就和构造函数一样

declare class Greeter {
    constructor(greeting: string);

    greeting: string;
    showGreeting(): void;
}
枚举

使用 declare enum 定义的枚举类型也称作外部枚举(Ambient Enums)

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
导出

npm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中,使用 declare 不再会声明一个
全局变量,而只会在当前文件中声明一个局部变量。只有在声明文件中使用 export 导出,然后在使用方
import 导入后,才会应用到这些类型声明

  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块
export const name: string;
export function getName(): string;
export class Animal {
    constructor(name: string);
    sayHi(): string;
}
export enum Directions {
    Up,
    Down,
    Left,
    Right
}
export interface Options {
    data: any;
}

内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
在浏览器环境下我们能直接使用的对象有很多,下面只是一部分例子,具体可见 内置对象
类型相关:Boolean、Error、Date、RegExp
dom相关:Document、HTMLElement、Event、NodeList

注意,TypeScript 核心库的定义中不包含 Node.js 部分。如果想用 TypeScript 写 Node.js,
则需要引入第三方声明文件@types/node

深入

深入了解Typescript可以看下面这篇文章:https://jkchao.github.io/type...


comer
385 声望6 粉丝

做难事必有所得