十分钟学会typescript【语法文档】

4,176 阅读10分钟

JavaScript 与 TypeScript 的区别

TypeScript 扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。

TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。

typescript的好处

TypeScript 增加了代码的可读性和可维护性

typescript是javascript的超集,能够使得js像java等语言一样有静态类型定义特点,它可以在编译阶段就发现大部分错误,并且还增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等

TypeScript 拥有活跃的社区

除此之外,ts还支持大部分第三方库,比如react,Google 开发的 Angular2 就是使用 TypeScript 编写的。

原始数据类型

在js中,我们知道数据类型一共有七种,分别是symbol、null、undefined、boolean、number、string、object。

这一小结我将介绍TS语法来显式定义五种简单数据类型

布尔值

let bool:boolean=true //正确编译
let boo2:boolean=2 //报错,类型错误

number

let num:number=1 //正确编译
let num2:number=true //报错,类型错误

string

let str:string='字符串' //正确编译
let str2:string=2 //报错,类型错误

undefined

let u:undefined=undefined //正确编译
let u2:undefined=null  //正确编译
let u3:undefined='string'  //编译错误

上面的代码都事先定义变量名的类型,如果数据本身是undefined,那么可以赋值给任何定义为其他类型的变量

let number:number=undefined //正确编译
let string:string=undefined //正确编译
let bool:boolean=undefined //正确编译
let n:null=undefined //正确编译
let u:undefined=undefined //正确编译

null

null跟undefined也是一样的

let n:null=null //正确编译

null和undefined都属于其他类型的子集,所以可以赋值给任何定义为其他类型的变量

let number:number=null //正确编译
let string:string=null //正确编译
let bool:boolean=null //正确编译
let n:null=null //正确编译
let u:undefined=null //正确编译

void

这里需要注意一点,在JS中是没有void这个概念的,但是在TS中,可以使用void来定义一个没有返回值的函数

function fn(): void {
  console.log(111);
}
const fn2 = (): void => {
  return 123; .//报错,不能有返回值
};

任意值

上面介绍了显式定义原始数据类型,TS还支持任意值any,也就是说使用它,你可以用来定义任意数据类型

const n: any = 123; //正确编译
const b: any = true; //正确编译
const str: any = "string"; //正确编译
const u: any = undefined; //正确编译
const nu: any = null; //正确编译

未声明类型的变量

如果一个变量被声明但是没有定义类型没有被赋值,那么就默认它是一个任意值

let anyThing;//等价于 ==> let anyThing:any
anyThing = 7;
anyThing = true;
anyThing = "string";

类型推论

上面说到如果声明了但是没有定义类型没有被赋值,默认是任意值。如果赋值了,就默认其为其数据值相应数据类型

let style = 7; // 等价于 ==> let style:number = 7
style = "string"; //报错

联合类型

联合类型就是同时定义多种类型,使用|来进行联合类型定义

let a: string | number;
a = "string"; //正确
a = 8; //正确
a = true; //报错

访问联合类型的属性或方法

当TS不知道我们定义的联合属性到底取哪一个时,我们只能访问他们共有属性,否则会报错

let a: string | number;
a.length // 报错,因为length不属于共有属性
a.toString // 正确,因为它们都有原型链中的toString方法

如果TS知道取哪一个,那么会启动类型推论,定义类型

let a: string | number;
a = "string"; //正确编译
a.length; //正确编译
a = 1; //正确编译
a.length; //报错

对象类型

我们使用接口(Interfaces)来定义对象的类型,以下示例对于对象的形状(Shape)进行描述

interface Person {
  name: string;
  age: number;
}
let obj: Person = {
  name: "qiuyanxi",
  age: 28
};

以上示例中定义了一个Person接口(接口首字母大写),接着定义一个对象obj,它的类型定义为Person,这样obj的形状就必须与接口Person一致。

比接口多一个属性是不被允许的

let obj: Person = {
  name: "qiuyanxi",
  age: 10,
  height:1 //报错
};

比接口少一个属性也是不被允许的

let obj: Person = { //报错
  name: "qiuyanxi", 
};

赋值的时候,变量的形状(Shape)必须与接口的形状保持一致

可选属性

但是有时候我们又不一定想要完全匹配形状,可以使用可选属性,使用一个?来描述可选属性

interface Person {
  name: string;
  age?: number;
}
let obj: Person = {
  name: "qiuyanxi",
};

可选属性的意思是这个属性可以不存在,但是不能新增属性,这样是不被允许的。

interface Person {
  name: string;
  age?: number;
}
let obj: Person = {
  name: "qiuyanxi",
  age:28,
  height:1 //报错
};

任意属性

有时候我们又需要有任意属性,我并不想定义内部太多的属性名,那么就可以用到任意属性

interface Person {
  name: string;
  age?: number;
  [propName: string]: any;
}
let obj: Person = {
  name: "yanxi",
  age: 28,
  firstName: "qiu"
};

上面示例中,使用 [propName: string]: any来定义任意类型,那么在下面就可以使用string类型的属性和任意类型的值

但是需要注意的一点,确定了任意属性后,那么确定的属性和可选的属性都必须是它的子集

interface Person {
  name: string;
  age?: number; //编译报错,number跟string冲突
  age2?:string;//正确编译
  [propName: string]: string;
}
const obj: Person = {
  name: "qiu",
  height: "123",
};

上面例子中,要求确定的属性name和可选属性age都属于它的任意属性的子集,由于number不是string的子集,所以报错了。

一个接口只能有一个任意属性,如果要同时有多个类型,那么可以用联合属性

interface Person {
  name: string;
  age?: number;
  [propName: string]: number | string;
}

let tom: Person = {
  name: "qiu",
  height: 2,
};

只读属性

如果我们希望一个属性允许只读,不允许编辑,那么可以使用readonly来定义只读属性。

interface Person {
  readonly name: string;
  age?: number;
  [propName: string]: number | string;
}

let tom: Person = {
  name: "qiu",
  height: 2,
};
tom.name = "tom";//编译错误 无法分配到 "name" ,因为它是只读属性

可以看到如果想要对此属性重新赋值就会报错

数组类型

在TS中,如果要对数组进行定义,可以使用“类型+[]”的形式来表示。

let array: number[] = [1, 2, 3];

上面定义了一个数组,数组里都要求为number类型

let array: number[] = ["1", 2, "3"];
//报错 不能将类型“number”分配给类型“string”。ts(2322)

如果事先定义好了类型,当使用数组的一些方法时,也会编译报错

let array: string[] = ["1", "2", "3"];
array.push(1);//类型“number”的参数不能赋给类型“string”的参数。

上面的示例要求我们push一个string类型

伪数组

伪数组的定义方式不跟数组一样

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

在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性

事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:

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

其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

数组中的any

let arr: any[] = [1, 2, 3, 4, { age: 1 }];

使用any来表示数组中可以存在任意的类型

函数类型

在js中,有函数表达式和函数声明两种定义方式

function fn(){} //函数声明
const fn=()=>{} //表达式

函数声明的定义类型方式

在函数声明时,我们可以对函数的输入和输出进行声明

function fn(x: number, y: string): string {
  return x + y;
}
fn(1, "2");

不允许多参数或者少参数

function fn(x: number, y: string): string {
  return x + y;
}
fn(1, "2", 1);//报错 应有 2 个参数,但获得 3 个。ts(2554)

对返回的值也有要求

function fn(x: number, y: string): number { //报错
  return x + y; 
}
fn(1, "2");

由于x+y的结果为string,所以不能定义number类型

表达式的定义类型方式

const fn = (x: number, y: number): string => {
  return (x + y).toString();
};

上面这种定义方式没有问题不会报错,不过实际上只是对匿名函数进行了定义,对于fn只是通过ts推论出来的,可以手动进行设置。

const fn: (x: number, y: number) => string = (
  x: number,
  y: number
): string => {
  return (x + y).toString();
};

太长了~

可选参数

我们也可以使用?来定义一个可选参数,不过需要注意的是,可选参数一定要放到必须参数的后面

function fn(x: number, y?: number): number {
  return x + y;
}

默认参数

function fn(x: number, y: number = 8): number {
  return x + y;
}
fn(1);

剩余参数

在es6中,我们可以使用...rest来获取剩余参数

function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}

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

事实上,items 是一个数组。所以我们可以用数组的类型来定义它:

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

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

重载

重载就是根据参数的不同(数量、类型)来做不同的处理

function fn(x: number | string, y: number): number | string {
  if (typeof x === "number") {
    return x + y;
  } else {
    return y.toString() + x;
  }
}

上面代码就是根据参数类型不同所做的重载+联合类型,但是这样并不够精确。精确的写法应该是这样的

function fn(x: number, y: number): number;
function fn(x: string, y: number): string;
function fn(x: string | number, y: number): number | string {
  if (typeof x === "number") {
    return x + y;
  } else {
    return y.toString() + x;
  }
}

上例中,我们重复定义了多次函数 fn,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。

ECMAScript 的内置对象

ECMAScript 标准提供的内置对象有:

Boolean、Error、Date、RegExp 等。

我们可以在 TypeScript 中将变量定义为这些类型:

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

而他们的定义文件,则在 TypeScript 核心库的定义文件中。

DOM 和 BOM 的内置对象

DOM 和 BOM 提供的内置对象有:

Document、HTMLElement、Event、NodeList 等。

TypeScript 中会经常用到这些类型:

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

type

类型别名

类型别名就是给类型取个名字,这种方式很方便帮助我们来解决联合类型的代码重复问题

type a = number | string;
type obj = { name: string; age: number };
let b: a = 123;
let c: a = "123";
let obj1: obj = {
  name: "qiu",
  age: 18,
};

上面的方式就是定义了联合类型以及定义了对象的类型,这种方式使得不需要写重复的代码

字符串字面量类型

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

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

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错

上面报错是因为dblclick不属于EventNames字符串字面量类型中的一种

参考文档

ts.xcatliu.com/

www.runoob.com/typescript/…