上篇简单介绍了TS及其类型定义。
如果没有明确的指定类型,TS会依照类型推论(Type Inference)的规则推断出一个类型:
let myFavoriteNumber = 'seven'; //无错
myFavoriteNumber = 7; //Type 'number' is not assignable to type 'string'.
注意:与声明不赋值区分,声明不赋值会设为任意值类型:any
联合类型使用 | 分隔每个类型,表示可以赋值为其中的一种。
let myFavoriteNumber: string | number; //允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。
myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // 无错
注意:当 TS 不确定一个联合类型的变量到底是哪个类型的时候(比如定义一个方法时),我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {
return something.length; // Property 'length' does not exist on type 'string | number'.
// length 不是 string 和 number 的共有属性,所以会报错。
// 但访问 string 和 number 的共有属性不会报错,如:return something.toString();
当联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型,此时访问该类型的属性不会报错。
在 TS 中,使用接口(Interfaces)来定义对象的类型。
接口Interfaces是对行为的抽象,而具体如何行动是由类classes去实现(implement)。
TS 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
如:定义一个接口 Person,接着定义一个变量 tan,它的类型是 Person。这样,就约束了 tan 的形状必须和接口 Person 一致(多一些或少一些属性都会报错):
interface Person {
name: string;
age: number;
}
let tan: Person = {
name: 'tan',
age: 22
};
当一个接口中有些属性是不需要的,将其设为可选属性,就可以不要它:
interface Person {
name: string;
age?: number; //设为可选属性
}
let tan: Person = {
name: 'tan'
};
注意:如果有任意属性,那么确定属性和可选属性的类型都必须是任意属性的类型的子集。
interface Person {
name: string;
[propName: string]: any; //定义了任意属性,取 string 类型,属性值取any类型。
}
let tom: Person = {
name: 'tan',
gender: 'male'
};
如果要求对象中的一些字段只能在创建的时候被赋值,使用 readonly 定义只读属性(只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候):
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527; // Cannot assign to 'id' because it is a constant or a read-only property.
TS中,数组有多种定义方式。
let fibonacci: number[] = [1, 1, 2, 3, 5]; //数组的值只能是number类型
let list: any[] = ['Xcat Liu', 25]; //数组的值可以是任意类型
注意:数组的项不允许出现其他类型,并且数组的一些方法的参数也不能出现其他类型,如:push()
2. 方法二:数组泛型:let fibonacci: Array
3. 方法三:用接口描述数组:
interface NumberArray {
[index: number]: number; //只要 index 的类型是 number,那么值的类型必须是 number。
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
function sum() {
let args: number[] = arguments;//错误,arguments缺少属性push
}
// index.ts(2,7): error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
// Property 'push' is missing in type 'IArguments'.
事实上常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function sum() {
let args: IArguments = arguments;
}
在 JS 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression)。
注意:参数过多或参数不足会报错
function sum(x: number, y: number): number {
return x + y;
}
注意:
在 TS 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,括号右边是输出类型。
在 ES6 中,=> 是箭头函数。
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
接口可以描述对象,数组,同样适用于函数:
interface SearchFunc { //定义一个接口
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc; //定义mySearch类型为SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
// 相当于接口的属性是函数的参数,返回值是接口的属性值。
同接口的可选属性一样,用 ? 表示可选的参数。
注意:可选参数必须接在必需参数后面,即可选参数后不能有必须参数。
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
在ES6中,允许给函数的参数添加默认值,TS 会将添加了默认值的参数识别为可选参数,因此可选参数可以不必须接在必需参数后面。
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
同样在ES6中,可以使用 …rest 的方式获取函数中的所有剩余参数:
function push(array: any[], ...items: any[]) {
items.forEach(function(item) { ////将items的每一项push进array
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3); // a是一个数组,...items就是剩余的参数1,2,3
函数重载允许一个函数接受不同数量或类型的参数,并作出不同的处理。
注意:TS 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。如:
//定义三个重载函数,实现数字或字符串的反转
//虽然利用联合类型可以实现此功能,但使用函数重载更能精准表达!使用时可以看到三个此函数的提示。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
类型断言(Type Assertion)是开发者手动指定一个值的类型:
<类型>值
或值 as 类型
注意:在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用值 as 类型
。
例如:将一个联合类型的变量指定为一个更加具体的类型(但不能指定为联合类型中不存在的类型):
// 使用联合类型时,必须使用这些类型共有的属性才行,但使用类型断言使其确定为某一种类型,就没有了此限制。
function getLength(something: string | number): number {
if ((something).length) { //something类型断言为字符串,就可以使用字符串独有的属性方法。
return (something).length;
} else {
return something.toString().length;
}
}
注意:类型断言不是类型转换。
JS 中有很多内置对象,这些内置对象可以直接在 TS 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 标准提供的内置对象有:Boolean
、Error
、Date
、RegExp
等。更多的内置对象。
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
DOM
和 BOM
提供的内置对象有:Document
、HTMLElement
、Event
、NodeList
等。
TS 中使用这些类型:
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
TypeScript 核心库的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在 TS 中的。比如常用的Math.pow()
,已经被预定义好类型:
interface Math {
pow(x: number, y: number): number;
}
注意,TypeScript 核心库的定义中不包含 Node.js 部分。
Node.js 不是内置对象的一部分,所以要用 TS 写 Node.js就则需要引入第三方声明文件:npm install @types/node --save-dev
当使用第三方库时,需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。具体见声明文件章节。