一、基本
1、Tuple 元组
元组类型允许表示一个已知元素数量和类型的数组。越界访问时会使用已定义类型的联合类型。
let tuple: [string, number];
tuple = ['tuple', 100]; // 赋值时需要提供所有元组类型中指定的项。
tuple[2] = 'Jane'; // OK
tuple[3] = 3; // OK
2、enum 枚举
enum类型是对JavaScript标准数据类型的补充
enum Color { Red, Green, Yellow, Blue } // 默认从0开始赋值
let c: Color = Color.Red; // 0
也可手动赋值
enum Color1 { Red = 1, Green, Yellow, Blue } // 从1开始
enum Color2 { Red = 1, Green = 6, Yellow = 3, Blue = 2 } // 或全部自定义赋值
enum Color3 { Red = 1, Green = 1.5, Yellow, Blue } // 赋值小数
console.log(Color3.Yellow); // 2.5
console.log(Color3[2.5]); // 'Yellow'
注:未手动赋值的枚举项会接着上一个枚举项递增1。因此可能出现值覆盖情况,应避免值覆盖情况出现。
3、undefined & null
默认情况下null和undefined是所有类型的子类型。当指定了strictNullChecks标记,null和undefined只能赋值给void和它们各自。
4、类型推断
如果没有明确指定类型,typescript会依照类型推论的规则推断出一个类型。
let name = 'Jane';
name = 7; // error
等价于
let name: string = 'Jane';
name = 7; // error
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型。
let name;
name = 'Jane'; // ok
name = 999; // ok
name = true; // ok
5、联合类型
表示取值可以为多种类型中的一种,使用 | 分隔每个类型。
使用联合属性时,当ts无法推断一个联合类型的变量的确切类型时,我们只能访问此联合类型的所有类型里共有的属性或方法。
function getLength(something: string | number): number {
return something.length; // error, number不具有length属性
}
function getString(something: string | number): string {
return something.toString(); // ok
}
6、数组
(1)数组类型表示方法
- 类型 + 方括号 → String[]
- 数组泛型 Array → Array
- 接口表示法(不常用)
interface StringArray {
[index: number]: string;
}
(2)arguments类数组描述
function add() {
let args: {
[index: number]: any;
length: number;
callee: Function;
} = arguments;
}
上例等价于(实际上,常用的类数组都有子集的接口定义,如IArguments, NodeList, HTMLCollection等):
function add() {
let args: IArguments = arguments;
}
/**
* interface IArguments {
* [index: number]: any;
* length: number;
* callee: Function;
* }
*/
7、函数
一个函数有输入和输出,因此在定义类型时,对输入和输出都需要考虑。
(1)函数声明
function sum(x: number, y: number): number {
return x + y;
}
(2)函数表达式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
在TypeScript中, => 用来表示函数的定义。上述等价于:
interface SumFunc {
(x: number, y: number) => number
}
let mySum: SumFunc;
mySum = function (x: number, y: number): number {
return x + y;
};
注意:函数参数中的可选参数(y?: number)必须接在必需参数后面。即可选参数后面不可再出现必需参数。
(3)参数默认值
在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受「可选参数必须接在必需参数后面」的限制了。
即可发现,可确定有值的属性应放在无法确认是否有值的参数前面。
function sum(x: number = 1, y: number): number {
return x + y;
}
(4)剩余参数rest
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
(5)函数重载
// 函数定义
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('');
}
}
TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
8、类型断言 value as type && value (在jsx中只可使用as类型)
类型断言常见使用场景:(类型断言表示开发者明确知道当前属性的类型,ts编译器不会对其进行校验。因此,如果断言不当,在运行时可能会导致报错。)
(1)当使用了联合类型时,在还不确定类型时就访问其中一个,这时可将当前类型断言为联合类型中的一个
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
(2)将父类断言为更加具体的子类
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
// 父类Error中不存在code属性,如果直接使用会报错
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
(3)将类型断言为any
window.foo = 1;
上面的例子中,我们需要将 window 上添加一个属性 foo,但 TypeScript 编译时会报错,提示我们 window 上不存在 foo 属性。 此时我们可以使用 as any 临时将 window 断言为 any 类型
(window as any).foo = 1;
(4)将any断言为其他具体类型
let pig: any = 'zhu';
let p = (pig as string); // 'zhu',此时P被断言为string类型
p = 1; // error
p = 'pig'; // ok
二、类和接口
相关概念:
类: 定义了一件事物的抽象特点,包含它的属性和方法。
抽象类:抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。
接口:不同类之间公有的属性或方法,可以抽象成一个接口。
接口可以被类实现。一个类只能继承自另一个类,但是可以实现多个接口。
1、只读属性
有一些属性只能在对象刚刚创建的时候修改其值。可用readonly来指定只读属性,指定后一旦赋值,就不能再改变了。
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 1, y: 2 };
p1.x = 3; // error!
const vs readonly
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。
做为变量使用的话用 const,若做为属性则使用readonly。
2、任意属性
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; // 表示带有任意数量的其他属性
}
const sq: SquareConfig = {
color: 'red',
height: 18,
}
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是该类型的子集。
3、类实现接口
一个类可以实现多个接口
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car Alarm');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
三、其他
1、interface vs type
相同点:
(1)都可以用于描述对象/函数(注意写法差别)
interface:
interface Person {
name: string;
age: number;
}
interface CreatePerson {
(name: string, age: number): void;
}
type:
type Person = {
name: string;
age: number;
}
type CreatePerson = (name: string, age: number) => string;
(2)都可用于类型继承
interface继承:
①interface extends interface:
interface Name {
name: string;
}
interface Person extends Name {
age: number
}
②interface extends type:
type Name = {
name: string;
}
interface Person extends Name {
age: number
}
type继承:
① type extends type
type Name = {
name: string
}
type Person = Name & { age: number };
② type extends interface
interface Name = {
name: string
}
type Person = Name & { age: number }
不同点:
(1) interface可以而type不行(不完全举例)
interface Person {
name: string;
age: number;
}
interface Person {
weight: number;
}
/**
* 上述Person接口会进行声明合并
* interface Person {
* name: string;
* age: number;
* weight: number;
* }
*/
(2) type可以而interface不可以(不完全举例)
可定义基础类型别名:
type newString = string;
const name: newString = 999; // error
const name1: newString = 'Jane'; // ok
联合类型:
type stringOrNumber = string | number;
type Text = string | { text: string }
定义字符串字面量类型:
type EventNames = 'click' | 'mousemove' | 'scroll';