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];
数组
- 「类型 + 方括号」表示法
let xxx: number[] = [1, 1, 2, 3, 5]; // 数组中只能输入数字类型
let xxx: any[] = ['block xun', 18, { data: 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]
- 接口形式表示
不推荐使用接口形式表达,因为复杂
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 let
和 declare const
,let
和var
没有什么区别
声明语句中只能定义类型,不能在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. interface
和 type
除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用 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;
}
泛型接口
泛型接口有两周表达的方式
- 将泛型放在接口的内部
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']
- 将泛型提前到接口名上(更方便给泛型传值)
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];
数组
- 「类型 + 方括号」表示法
let xxx: number[] = [1, 1, 2, 3, 5]; // 数组中只能输入数字类型
let xxx: any[] = ['block xun', 18, { data: 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]
- 接口形式表示
不推荐使用接口形式表达,因为复杂
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 let
和 declare const
,let
和var
没有什么区别
声明语句中只能定义类型,不能在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. interface
和 type
除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用 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;
}
泛型接口
泛型接口有两周表达的方式
- 将泛型放在接口的内部
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']
- 将泛型提前到接口名上(更方便给泛型传值)
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;
}