欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

typescript文档整理

程序员文章站 2024-03-21 13:48:46
...

安装

安装
npm install -g typescript

转换
cmd --> tsc xxx.ts //转换为js

基础操作

原始数据类型

let xxx: boolean = false
let xxx: number = 11
let xxx: string = 'xxx'
let xxx: undefined = undefined
let xxx: null  = null
let xxx: any  = 'xxx'

原始数据类型包括:boolean、number、string、null、undefined 以及 Symbol(还有any)

类型推论: 如果没有声明类型,typescript会推测一个类型
如果定义时没有类型也没有赋值,都会被类型推论成any(任何)

空值

void为空值,如果函数没有返回值的话,可以写void

function alertName(): void {
    alert('');
}

如果定义一个空值,只能赋值为null或undefined

联合类型

let xxx:string|number = xxx

可以设置xxx的类型为几种类型都可以
如果函数的形参为联合类型,则在函数中对形参的操作必须是联合类型*有的方法,否则会报错

报错:

function fun(data:string|number) :any {
    return data.length
}
==>
error: 类型“string | number”上不存在属性“length”

接口(Interfaces)

表示对象

interface接口,可以预设一个类型
接口一般首字母大写

基础用法:

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

let tom: Person = {
    name: 'Tom',
    age: 25
};
  • 可选属性: 可以设置object中的某些函数不一定存在
  • 任意属性: 可以添加任意多个符合条件的属性
    如果可选属性和任意属性同时存在,任意属性的条件必须包括可选属性(如果同时存在,可选属性本身也就没有什么意义了)
  • 只读属性: 必须在创建时赋值,赋值后不能修改,否则会报错(创建方法: readonly xxx: any)
interface Person {
    name: string;
    age?: number; // 可选属性
    [propName: string]: string|number; // 任意属性,条件是string|number
    readonly id: number; // 只读属性
}

也可以现做现用

let data = {
    a: 1,
    b: 2,
    c: '33'
}
let data2: {
    a: number;
    b: number;
    c: string|number;
} = data

interface中分隔符为“;”,对象为“,”

表示数组

typescript创建数组的方式在下边
虽然接口也可以用来描述数组,但是一般不会这么写,因为这种方式复杂
数组内元素的数据类型一般情况下都是相同的(any[]类型除外),如果想要不同的,可以使用元组

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

数组

  1. 「类型 + 方括号」表示法
let xxx: number[] = [1, 1, 2, 3, 5];    // 数组中只能输入数字类型
let xxx: any[] = ['block xun', 18, { data: 1 }];  	// 任意类型数组
  1. 数组泛型表示法

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

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

在此处为泛型表示,在调用时可以指定“T”为具体的类型,如上个例子,将T指定成了number

定义泛型时,可以一次性定义多个类型参数

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
  1. 接口形式表示

不推荐使用接口形式表达,因为复杂

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

我将类数组单独分出一个模块来记录,因为类数组的定义方式和数组区别很大,单独拿出来更容易理解

类数组不是数组类型,定义时应该使用接口

arguments例子:

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

或者使用内置的已经定义好的接口 IArguments

function sum() {
    let args: IArguments = arguments;
}

IArguments时typescript中定义好的内置对象,他实际上就是上边例子中创建的接口
更多内置对象,官网链接https://ts.xcatliu.com/basics/built-in-objects

函数

常见的有两种定义方式,函数声明和函数表达式(和js一样)
执行时函数输入的形参,必须和创建时写的形参数量相一致

// 函数声明
    function sum(x: number, y: number): number {
    return x + y;
}
// 表达式
    let mySum = function (x: number, y: number): number {
    return x + y;
};

在表达式方法中

let mySum = function (x: number, y: number): number {
    return x + y;
};

上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意:=>不是箭头函数,而是设定函数返回值为number,和上边的:number相同
个人理解为,一般情况下,=>在=左边代表设定返回值,在=右边代表箭头函数

用接口定义函数

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

可选参数设置

?为可选参数,可选参数必须放在最后

function fun (data1:string,data2?:string) {
    return "xxx"
}

默认值

function fun (data:string = 'xxx') {}

剩余参数

在创建参数时,可以设置剩余参数,当执行时输入的非定义的参数都会进入剩余参数这个数组中

function fun(data:string, ...data2:any[]) {}
fun('data','1','1','1','1','1','1')
// data = 'data'
// data2 = ['1','1','1','1','1','1']

剩余参数为一个数组,数组中包括所有非形参的值

重载

重载允许一个函数在接受不同参数的情况下做出不同的处理

// 要求:在输入string时输出string,在输入number时输出number
function fun (data:number):number;
function fun (data:string):string;
function fun (data:number | string):number | string {
    ...
    return data
}

前二次是函数定义,最后一次是函数实现,如果函数定义有包含关系,需要优先把精确的定义写在前边

在文章底部有详细的重载和合并的解释

类型断言

两种写法

<类型>值

值 as 类型

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种

在某种情况下,我们需要在还不确定类型的时候访问其中一个类型的属性或方法

function fun (data : string | number) {
    if ((<string> data).length) {     // 这时需要加()将断言括起来
        return (<string> data).length
    } else {
        return data
    }
}

断言并不会改变类型,只会让之后的代码在typescript中不报错

断言 和 泛型 的区别

断言:

<类型>值
值 as 类型

泛型:
值<类型>

声明语句和声明文件

在HTML中引入jQuery后,ts并不知道$jQuery是什么,所以需要声明一下

declare var jQuery: (selector: string) => any;

推荐使用第三方声明文件库 @types
第三方库中的文件不需要自己声明,已经声明好的
如types中的jQuery库 npm install @types/jquery --save-dev
可以在这个页面搜索你需要的声明文件

当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了

在不同的场景下,声明文件的内容和使用方式会有所区别。

库的使用场景主要有以下几种:

  • 全局变量:通过 <script> 标签引入第三方库,注入全局变量
  • npm 包:通过 import foo from ‘foo’ 导入,符合 ES6 模块规范
  • UMD 库:既可以通过 <script> 标签引入,又可以通过 import 导入
  • 直接扩展全局变量:通过 <script> 标签引入后,改变一个全局变量的结构
  • 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
  • 模块插件:通过 <script> 或 import 导入后,改变另一个模块的结构

我们通常会把声明语句放到一个单独的声明文件中
声明文件必须以.d.ts结尾

// src/index.d.ts
declare var jQuery: (selector: string) => any;

1. declare var

声明全局变量

在HTML中引入jQuery后,ts并不知道$jQuery是什么,所以需要声明一下

declare var jQuery: (selector: string) => any;  // 相当于函数表达式方式定义jQuery, any代表返回值为任何值
// 声明后才可使用
jQuery('#div')

declare出来的东西并没有真正定义变量,只是用于编译时检查,上边代码块编译后相当于js中的

jQuery('#div')

还有 declare letdeclare const,letvar没有什么区别
声明语句中只能定义类型,不能在declare后写具体的函数

2. declare function

declare function 用来定义全局函数的类型。jQuery 其实就是一个函数,所以也可以用 function 来定义(函数声明方式定义jQuery)

declare function jQuery(xxx:string): any
// 执行
jQuery('#foo');

声明语句中支持函数重载

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;

3. declare class

定义一个全局类

declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}
// 执行
let cat = new Animal('Tom');

4. declare enum外部枚举

定义外部枚举
declare enum xxx {
a,
b,
c
}
使用:xxx.a

5. interfacetype

除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用 interface 或 type 来声明一个全局的接口或类型

// src/jQuery.d.ts
interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any;
}
declare namespace jQuery {
    function ajax(url: string, settings?: AjaxSettings): void;
}

这样的话,在其他文件中也可以使用这个接口或类型了:

// src/index.ts
let settings: AjaxSettings = {
    method: 'POST',
    data: {
        name: 'foo'
    }
};
jQuery.ajax('/api/post_something', settings);

export 导入

待完善
export const name: string;
export function getName(): string;
export interface data {
data1: any
}
import { xxx } from ‘xxx’

export default
同上(导出值为对象之前,要先declare enum定义出来)
ts推荐导入方法
import data = requirt(‘xxx’) // 整体导入
import bar = data.a // 单个导入

二部分
类型别名
type创建类型别名
type data = string;
function () {}

进阶部分

类型别名

type 类型别名用来给一个类型或联合类型起一个新名字

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

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

字符串字面量类型

type 字符串字面量类型用来约束取值只能是某几个字符串中的一个。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

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

因为上边定义了EventNames只能是’click’,‘scroll’,'mousemove’之中的一个,所以第二次调用报错了

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

元组

typescript数组中元素的数据类型一般都是相同的(any[]类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组

创建一个元组

var tuple = [10, "data"];

// 或

let tuple: [number, string] = [10, "data"];

// 或

let tuple: [number, string];
tuple[0] = 10;
tuple[1] = "data";

// 也可以只赋值一项

let tuple: [number, string];
tuple[0] = 10;

当添加越界(过多)的元素时,他的类型会被限制为元组中所有写过的类型的联合类型

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');  // 正常添加,不会报错
tom.push(true);  // 这时会报错,因为这个元组中定义了只能添加string和number类型

枚举

JavaScript中本身没有枚举类型,枚举能够给一系列数值集合提供友好的名称,也就是说枚举可以表示一个命名元素的集合。

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

枚举成员会被赋值为从 0 开始递增的"索引",同时"索引"也会反向赋值

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

上边的例子会被编译为:

var Days;
(function (Days) {
    Days[Days["Sun"] = 0] = "Sun";
    Days[Days["Mon"] = 1] = "Mon";
    Days[Days["Tue"] = 2] = "Tue";
    Days[Days["Wed"] = 3] = "Wed";
    Days[Days["Thu"] = 4] = "Thu";
    Days[Days["Fri"] = 5] = "Fri";
    Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

也可以手动赋值给枚举类型

enum Days {Sun = 7, why = <any>"wow", Mon = 20, Tue = 18, Wed, Thu, Fri, Sat, fun = <any>("fun" + "fun"), len = "len".length};

没有手动赋值的项会根据上一项的枚举值继续递增
如果手动赋值的值和自动递增的值重复了,typescript不会做出任何处理
手动赋值项可以不是数字类型,甚至可以使用表达式,但需要使用断言来无视类型检查
注意手动赋值如果不是数字类型,而且后边是一个自动生成值得参数,则会因为生成不了参数而报错

编译成js后...
var Days;
(function (Days) {
    Days[Days["Sun"] = 7] = "Sun";
    Days[Days["why"] = "wow"] = "why";
    Days[Days["Mon"] = 20] = "Mon";
    Days[Days["Tue"] = 18] = "Tue";
    Days[Days["Wed"] = 19] = "Wed";
    Days[Days["Thu"] = 20] = "Thu";
    Days[Days["Fri"] = 21] = "Fri";
    Days[Days["Sat"] = 22] = "Sat";
    Days[Days["fun"] = ("fun" + "fun")] = "fun";
    Days[Days["len"] = "len".length] = "len";
})(Days || (Days = {}));

常数枚举

  • 常数枚举只能自动生成
  • 常熟枚举会在编译阶段被删除
const enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

外部枚举也会在编译阶段被删除

class 类

和ES6中正常的class类的使用方法类似,不过有些地方有区别

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的

  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
    只能在类内部访问,不能在实例(new)和继承(extend)中使用,如果构造函数(constructor)被设定为该类型,则该类不允许被继承或实例化

  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
    能在类内部访问,也可以在继承(extend)中使用,不能在实例(new)中使用

  • readonly 只读属性关键字,只允许出现在属性声明或索引签名中。(如果和上三个之一同时使用的话,要把readonly写在那三个之后)

示例:
public:

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

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

private:

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}
class Cat extends Animal {  // 继承
    constructor(name) {
        super(name);
        console.log(this.name); // 会报错
    }
}
let a = new Animal('Jack'); // 实例
console.log(a.name); // 会报错

protected:

class Animal {
    public name;
    protected constructor (name) {
        this.name = name;
  }
}
class Cat extends Animal {
    constructor (name) {
        super(name);
    }
}

let a = new Animal('Jack'); // 会报错

如果想禁止继承和实例:

class Animal {
    public name;
    private constructor (name) { // 如果只想禁止实例,把private换成protected
        this.name = name;
  }
}
class Cat extends Animal { // 会报错
    ...
}
let a = new Animal('Jack'); // 会报错

readonly

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

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

抽象类

  • 抽象类不允许被实例化
  • 抽象类中的抽象方法必须在子类中实现(创建)
abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi()
}

class Cat extends Animal {
    public sayHi() { // 在此处创建了父类的抽象类sayHi
        console.log(`Meow, My name is ${this.name}`);
    }
}

let cat = new Cat('Tom');
cat.sayHi()

类和接口

在typescript中,可以用 implements 把接口提取成类的接口

interface Alarm {
    alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

一个类可以使用多个接口

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();
}

接口继承类

class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

泛型

  • 泛型是指 在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
let xxx: Array<number> = createArray<number>(5,4)
===>
xxx = [4,4,4,4,4]

在此处为泛型表示,在调用时可以指定“T”为具体的类型,如上个例子,将T指定成了number

定义泛型时,可以一次性定义多个类型参数

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]

泛型参数可以传入默认值

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

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
如下方例子,泛型 T不一定包含属性length,所以编译的时候报错了

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

使用extends进行泛型约束,如下例,只允许符合Lengthwise的的值(传入的值.length必须是number格式)

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

泛型接口

泛型接口有两周表达的方式

  1. 将泛型放在接口的内部
interface CreateArrayFunc {
    <T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc; // 定义的泛型在调用时并没有传值
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
  1. 将泛型提前到接口名上(更方便给泛型传值)
interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>; // 给泛型传入any值(也可以不传值,象上一个代码块那样写)
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

如果不给接口中的泛型传值,泛型会被“定义”为“T”型(T可以使任何类型,但也不是任何类型,也就是说T类型 = T类型,其他类型 != T类型)
比如:函数的一个形参为T类型,返回值为T类型,那么只有那个T类型的形参可以作为返回值

泛型类

泛型类的使用方法和正常泛型接口类似

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; };

重载与合并

重载

重复定义一个函数,函数会进行重载

重载允许一个函数在接受不同参数的情况下做出不同的处理

// 要求:在输入string时输出string,在输入number时输出number
function fun (data:number):number;
function fun (data:string):string;
function fun (data:number | string):number | string {
    ...
    return data
}

前二次是函数定义,最后一次是函数实现,如果函数定义有包含关系,需要优先把精确的定义写在前边

合并

重复输入一个一个接口或者类,会实现函数合并

简单实现的例子

interface Alarm {
    price: number;
}
interface Alarm {
    weight: number;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
}

如果合并的属性冲突,并且不一致,会报错

interface Alarm {
    price: number;
}
interface Alarm {
    price: string;  // 报错(如果这里是price: number;则不会报错)
    weight: number;
}

如果合并的函数冲突,则与函数重载效果一致

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

interface Alarm {
    price: number;
    alert(s: string): string;
    alert(s: number): number;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: number): number;
    alert(s: string, n: number): string;
}

安装

安装
npm install -g typescript

转换
cmd --> tsc xxx.ts //转换为js

基础操作

原始数据类型

let xxx: boolean = false
let xxx: number = 11
let xxx: string = 'xxx'
let xxx: undefined = undefined
let xxx: null  = null
let xxx: any  = 'xxx'

原始数据类型包括:boolean、number、string、null、undefined 以及 Symbol(还有any)

类型推论: 如果没有声明类型,typescript会推测一个类型
如果定义时没有类型也没有赋值,都会被类型推论成any(任何)

空值

void为空值,如果函数没有返回值的话,可以写void

function alertName(): void {
    alert('');
}

如果定义一个空值,只能赋值为null或undefined

联合类型

let xxx:string|number = xxx

可以设置xxx的类型为几种类型都可以
如果函数的形参为联合类型,则在函数中对形参的操作必须是联合类型*有的方法,否则会报错

报错:

function fun(data:string|number) :any {
    return data.length
}
==>
error: 类型“string | number”上不存在属性“length”

接口(Interfaces)

表示对象

interface接口,可以预设一个类型
接口一般首字母大写

基础用法:

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

let tom: Person = {
    name: 'Tom',
    age: 25
};
  • 可选属性: 可以设置object中的某些函数不一定存在
  • 任意属性: 可以添加任意多个符合条件的属性
    如果可选属性和任意属性同时存在,任意属性的条件必须包括可选属性(如果同时存在,可选属性本身也就没有什么意义了)
  • 只读属性: 必须在创建时赋值,赋值后不能修改,否则会报错(创建方法: readonly xxx: any)
interface Person {
    name: string;
    age?: number; // 可选属性
    [propName: string]: string|number; // 任意属性,条件是string|number
    readonly id: number; // 只读属性
}

也可以现做现用

let data = {
    a: 1,
    b: 2,
    c: '33'
}
let data2: {
    a: number;
    b: number;
    c: string|number;
} = data

interface中分隔符为“;”,对象为“,”

表示数组

typescript创建数组的方式在下边
虽然接口也可以用来描述数组,但是一般不会这么写,因为这种方式复杂
数组内元素的数据类型一般情况下都是相同的(any[]类型除外),如果想要不同的,可以使用元组

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

数组

  1. 「类型 + 方括号」表示法
let xxx: number[] = [1, 1, 2, 3, 5];    // 数组中只能输入数字类型
let xxx: any[] = ['block xun', 18, { data: 1 }];  	// 任意类型数组
  1. 数组泛型表示法

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

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

在此处为泛型表示,在调用时可以指定“T”为具体的类型,如上个例子,将T指定成了number

定义泛型时,可以一次性定义多个类型参数

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
  1. 接口形式表示

不推荐使用接口形式表达,因为复杂

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

我将类数组单独分出一个模块来记录,因为类数组的定义方式和数组区别很大,单独拿出来更容易理解

类数组不是数组类型,定义时应该使用接口

arguments例子:

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

或者使用内置的已经定义好的接口 IArguments

function sum() {
    let args: IArguments = arguments;
}

IArguments时typescript中定义好的内置对象,他实际上就是上边例子中创建的接口
更多内置对象,官网链接https://ts.xcatliu.com/basics/built-in-objects

函数

常见的有两种定义方式,函数声明和函数表达式(和js一样)
执行时函数输入的形参,必须和创建时写的形参数量相一致

// 函数声明
    function sum(x: number, y: number): number {
    return x + y;
}
// 表达式
    let mySum = function (x: number, y: number): number {
    return x + y;
};

在表达式方法中

let mySum = function (x: number, y: number): number {
    return x + y;
};

上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意:=>不是箭头函数,而是设定函数返回值为number,和上边的:number相同
个人理解为,一般情况下,=>在=左边代表设定返回值,在=右边代表箭头函数

用接口定义函数

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

可选参数设置

?为可选参数,可选参数必须放在最后

function fun (data1:string,data2?:string) {
    return "xxx"
}

默认值

function fun (data:string = 'xxx') {}

剩余参数

在创建参数时,可以设置剩余参数,当执行时输入的非定义的参数都会进入剩余参数这个数组中

function fun(data:string, ...data2:any[]) {}
fun('data','1','1','1','1','1','1')
// data = 'data'
// data2 = ['1','1','1','1','1','1']

剩余参数为一个数组,数组中包括所有非形参的值

重载

重载允许一个函数在接受不同参数的情况下做出不同的处理

// 要求:在输入string时输出string,在输入number时输出number
function fun (data:number):number;
function fun (data:string):string;
function fun (data:number | string):number | string {
    ...
    return data
}

前二次是函数定义,最后一次是函数实现,如果函数定义有包含关系,需要优先把精确的定义写在前边

在文章底部有详细的重载和合并的解释

类型断言

两种写法

<类型>值

值 as 类型

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种

在某种情况下,我们需要在还不确定类型的时候访问其中一个类型的属性或方法

function fun (data : string | number) {
    if ((<string> data).length) {     // 这时需要加()将断言括起来
        return (<string> data).length
    } else {
        return data
    }
}

断言并不会改变类型,只会让之后的代码在typescript中不报错

断言 和 泛型 的区别

断言:

<类型>值
值 as 类型

泛型:
值<类型>

声明语句和声明文件

在HTML中引入jQuery后,ts并不知道$jQuery是什么,所以需要声明一下

declare var jQuery: (selector: string) => any;

推荐使用第三方声明文件库 @types
第三方库中的文件不需要自己声明,已经声明好的
如types中的jQuery库 npm install @types/jquery --save-dev
可以在这个页面搜索你需要的声明文件

当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了

在不同的场景下,声明文件的内容和使用方式会有所区别。

库的使用场景主要有以下几种:

  • 全局变量:通过 <script> 标签引入第三方库,注入全局变量
  • npm 包:通过 import foo from ‘foo’ 导入,符合 ES6 模块规范
  • UMD 库:既可以通过 <script> 标签引入,又可以通过 import 导入
  • 直接扩展全局变量:通过 <script> 标签引入后,改变一个全局变量的结构
  • 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
  • 模块插件:通过 <script> 或 import 导入后,改变另一个模块的结构

我们通常会把声明语句放到一个单独的声明文件中
声明文件必须以.d.ts结尾

// src/index.d.ts
declare var jQuery: (selector: string) => any;

1. declare var

声明全局变量

在HTML中引入jQuery后,ts并不知道$jQuery是什么,所以需要声明一下

declare var jQuery: (selector: string) => any;  // 相当于函数表达式方式定义jQuery, any代表返回值为任何值
// 声明后才可使用
jQuery('#div')

declare出来的东西并没有真正定义变量,只是用于编译时检查,上边代码块编译后相当于js中的

jQuery('#div')

还有 declare letdeclare const,letvar没有什么区别
声明语句中只能定义类型,不能在declare后写具体的函数

2. declare function

declare function 用来定义全局函数的类型。jQuery 其实就是一个函数,所以也可以用 function 来定义(函数声明方式定义jQuery)

declare function jQuery(xxx:string): any
// 执行
jQuery('#foo');

声明语句中支持函数重载

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;

3. declare class

定义一个全局类

declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}
// 执行
let cat = new Animal('Tom');

4. declare enum外部枚举

定义外部枚举
declare enum xxx {
a,
b,
c
}
使用:xxx.a

5. interfacetype

除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用 interface 或 type 来声明一个全局的接口或类型

// src/jQuery.d.ts
interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any;
}
declare namespace jQuery {
    function ajax(url: string, settings?: AjaxSettings): void;
}

这样的话,在其他文件中也可以使用这个接口或类型了:

// src/index.ts
let settings: AjaxSettings = {
    method: 'POST',
    data: {
        name: 'foo'
    }
};
jQuery.ajax('/api/post_something', settings);

export 导入

待完善
export const name: string;
export function getName(): string;
export interface data {
data1: any
}
import { xxx } from ‘xxx’

export default
同上(导出值为对象之前,要先declare enum定义出来)
ts推荐导入方法
import data = requirt(‘xxx’) // 整体导入
import bar = data.a // 单个导入

二部分
类型别名
type创建类型别名
type data = string;
function () {}

进阶部分

类型别名

type 类型别名用来给一个类型或联合类型起一个新名字

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

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

字符串字面量类型

type 字符串字面量类型用来约束取值只能是某几个字符串中的一个。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

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

因为上边定义了EventNames只能是’click’,‘scroll’,'mousemove’之中的一个,所以第二次调用报错了

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

元组

typescript数组中元素的数据类型一般都是相同的(any[]类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组

创建一个元组

var tuple = [10, "data"];

// 或

let tuple: [number, string] = [10, "data"];

// 或

let tuple: [number, string];
tuple[0] = 10;
tuple[1] = "data";

// 也可以只赋值一项

let tuple: [number, string];
tuple[0] = 10;

当添加越界(过多)的元素时,他的类型会被限制为元组中所有写过的类型的联合类型

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');  // 正常添加,不会报错
tom.push(true);  // 这时会报错,因为这个元组中定义了只能添加string和number类型

枚举

JavaScript中本身没有枚举类型,枚举能够给一系列数值集合提供友好的名称,也就是说枚举可以表示一个命名元素的集合。

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

枚举成员会被赋值为从 0 开始递增的"索引",同时"索引"也会反向赋值

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

上边的例子会被编译为:

var Days;
(function (Days) {
    Days[Days["Sun"] = 0] = "Sun";
    Days[Days["Mon"] = 1] = "Mon";
    Days[Days["Tue"] = 2] = "Tue";
    Days[Days["Wed"] = 3] = "Wed";
    Days[Days["Thu"] = 4] = "Thu";
    Days[Days["Fri"] = 5] = "Fri";
    Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

也可以手动赋值给枚举类型

enum Days {Sun = 7, why = <any>"wow", Mon = 20, Tue = 18, Wed, Thu, Fri, Sat, fun = <any>("fun" + "fun"), len = "len".length};

没有手动赋值的项会根据上一项的枚举值继续递增
如果手动赋值的值和自动递增的值重复了,typescript不会做出任何处理
手动赋值项可以不是数字类型,甚至可以使用表达式,但需要使用断言来无视类型检查
注意手动赋值如果不是数字类型,而且后边是一个自动生成值得参数,则会因为生成不了参数而报错

编译成js后...
var Days;
(function (Days) {
    Days[Days["Sun"] = 7] = "Sun";
    Days[Days["why"] = "wow"] = "why";
    Days[Days["Mon"] = 20] = "Mon";
    Days[Days["Tue"] = 18] = "Tue";
    Days[Days["Wed"] = 19] = "Wed";
    Days[Days["Thu"] = 20] = "Thu";
    Days[Days["Fri"] = 21] = "Fri";
    Days[Days["Sat"] = 22] = "Sat";
    Days[Days["fun"] = ("fun" + "fun")] = "fun";
    Days[Days["len"] = "len".length] = "len";
})(Days || (Days = {}));

常数枚举

  • 常数枚举只能自动生成
  • 常熟枚举会在编译阶段被删除
const enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

外部枚举也会在编译阶段被删除

class 类

和ES6中正常的class类的使用方法类似,不过有些地方有区别

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的

  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
    只能在类内部访问,不能在实例(new)和继承(extend)中使用,如果构造函数(constructor)被设定为该类型,则该类不允许被继承或实例化

  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
    能在类内部访问,也可以在继承(extend)中使用,不能在实例(new)中使用

  • readonly 只读属性关键字,只允许出现在属性声明或索引签名中。(如果和上三个之一同时使用的话,要把readonly写在那三个之后)

示例:
public:

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

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

private:

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}
class Cat extends Animal {  // 继承
    constructor(name) {
        super(name);
        console.log(this.name); // 会报错
    }
}
let a = new Animal('Jack'); // 实例
console.log(a.name); // 会报错

protected:

class Animal {
    public name;
    protected constructor (name) {
        this.name = name;
  }
}
class Cat extends Animal {
    constructor (name) {
        super(name);
    }
}

let a = new Animal('Jack'); // 会报错

如果想禁止继承和实例:

class Animal {
    public name;
    private constructor (name) { // 如果只想禁止实例,把private换成protected
        this.name = name;
  }
}
class Cat extends Animal { // 会报错
    ...
}
let a = new Animal('Jack'); // 会报错

readonly

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

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

抽象类

  • 抽象类不允许被实例化
  • 抽象类中的抽象方法必须在子类中实现(创建)
abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi()
}

class Cat extends Animal {
    public sayHi() { // 在此处创建了父类的抽象类sayHi
        console.log(`Meow, My name is ${this.name}`);
    }
}

let cat = new Cat('Tom');
cat.sayHi()

类和接口

在typescript中,可以用 implements 把接口提取成类的接口

interface Alarm {
    alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

一个类可以使用多个接口

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();
}

接口继承类

class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

泛型

  • 泛型是指 在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
let xxx: Array<number> = createArray<number>(5,4)
===>
xxx = [4,4,4,4,4]

在此处为泛型表示,在调用时可以指定“T”为具体的类型,如上个例子,将T指定成了number

定义泛型时,可以一次性定义多个类型参数

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]

泛型参数可以传入默认值

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

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
如下方例子,泛型 T不一定包含属性length,所以编译的时候报错了

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

使用extends进行泛型约束,如下例,只允许符合Lengthwise的的值(传入的值.length必须是number格式)

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

泛型接口

泛型接口有两周表达的方式

  1. 将泛型放在接口的内部
interface CreateArrayFunc {
    <T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc; // 定义的泛型在调用时并没有传值
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
  1. 将泛型提前到接口名上(更方便给泛型传值)
interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>; // 给泛型传入any值(也可以不传值,象上一个代码块那样写)
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

如果不给接口中的泛型传值,泛型会被“定义”为“T”型(T可以使任何类型,但也不是任何类型,也就是说T类型 = T类型,其他类型 != T类型)
比如:函数的一个形参为T类型,返回值为T类型,那么只有那个T类型的形参可以作为返回值

泛型类

泛型类的使用方法和正常泛型接口类似

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; };

重载与合并

重载

重复定义一个函数,函数会进行重载

重载允许一个函数在接受不同参数的情况下做出不同的处理

// 要求:在输入string时输出string,在输入number时输出number
function fun (data:number):number;
function fun (data:string):string;
function fun (data:number | string):number | string {
    ...
    return data
}

前二次是函数定义,最后一次是函数实现,如果函数定义有包含关系,需要优先把精确的定义写在前边

合并

重复输入一个一个接口或者类,会实现函数合并

简单实现的例子

interface Alarm {
    price: number;
}
interface Alarm {
    weight: number;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
}

如果合并的属性冲突,并且不一致,会报错

interface Alarm {
    price: number;
}
interface Alarm {
    price: string;  // 报错(如果这里是price: number;则不会报错)
    weight: number;
}

如果合并的函数冲突,则与函数重载效果一致

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

interface Alarm {
    price: number;
    alert(s: string): string;
    alert(s: number): number;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
// 合并成
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: number): number;
    alert(s: string, n: number): string;
}

个人博客