typescript文档整理

安装

安装
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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值