TypeScripe学习
原始数据类型
原始数据类型包括:布尔值、数值、字符串、null
、undefined
以及 ES6 中的新类型 Symbol
在TS中,使用 : 指定变量的类型
1.布尔值
let isDone: boolean = false;
事实上 new Boolean()
返回的是一个 Boolean
对象:
let createdByNewBoolean: Boolean = new Boolean(1);
直接调用 Boolean
也可以返回一个 boolean
类型
let createdByBoolean: boolean = Boolean(1);
错误的写法
let createdByNewBoolean: boolean = new Boolean(1);//错误写法 let createdByNewBoolean: Boolean = new Boolean(1);//正确写法
2.数值
let decLiteral: number = 6; let hexLiteral: number = 0xf00d; // ES6 中的二进制表示法 let binaryLiteral: number = 0b1010; // ES6 中的八进制表示法 let octalLiteral: number = 0o744; let notANumber: number = NaN; let infinityNumber: number = Infinity;
编译结果:
var decLiteral = 6; var hexLiteral = 0xf00d; // ES6 中的二进制表示法 var binaryLiteral = 10; // ES6 中的八进制表示法 var octalLiteral = 484; var notANumber = NaN; var infinityNumber = Infinity;
3.字符串
let myName: string = 'Tom'; let myAge: number = 25; // 模板字符串 let sentence: string = `Hello, my name is ${myName}.I'll be ${myAge + 1} years old next month.`;
编译结果:
var myName = 'Tom'; var myAge = 25; // 模板字符串 var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";
4.null和undefined
null和undefined在typescript中分别是两个独立的类型限定:
let u: undefined = undefined; let n: null = null;
undefined限定的变量只能赋值undefined,null也一样。
与 void
的区别是,undefined
和 null
是所有类型的子类型。也就是说 undefined
类型的变量,可以赋值给 number
类型的变量
5.空值void
void限定的变量只能赋值null或者undefined
let unusable: void = undefined;
与null和undefined限定不同的是,null和undefined是所有类型的子类型:
let num: number = undefined; //这样没有问题 // 这样也不会报错 let u: undefined; let num: number = u;
而void类型的变量不能互相随意传递:
let noContent: void; let num: number = noContent; // 报错
任意值 any
允许赋值为任意类型
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。
let myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // 等价于下边 let myFavoriteNumber: string = 'seven'; myFavoriteNumber = 7;
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any
类型而完全不被类型检查:
let myFavoriteNumber; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
联合类型
表示取值可以为多种类型中的一种
联合类型使用 |
分隔每个类型
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
对象的类型——接口
在 TypeScript 中,我们使用接口来定义对象的类型
在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。
1.简单的例子
interface Person { name: string; age: number; } let tom: Person = { name: 'Tom', age: 25 };
上面的例子中,我们定义了一个接口 Person
,接着定义了一个变量 tom
,它的类型是 Person
。这样,我们就约束了 tom
的形状必须和接口 Person
一致。
定义的变量不允许多一个属性或者少一个属性。
赋值的时候,变量的形状必须和接口的形状保持一致。
2.可选属性
interface Person { name: string; age?: number; //可选属性 }
可选属性的含义是该属性可以不存在。
但是仍然不允许添加未定义的属性。
3.任意属性
interface Person { name: string; age?: number; [propName: string]: any; //任意属性 } let tom: Person = { name: 'Tom', gender: 'male' };
使用 [propName: string]
定义了任意属性取 string
类型的值。
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
name: string;
age?: number;
[propName: string]: string;//任意属性的值允许是 string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
上例中,任意属性的值允许是 string
,但是可选属性 age
的值却是 number
,number
不是 string
的子属性,所以报错了。
4.只读属性
interface Person { readonly id: number; //只读属性 name: string; age?: number; [propName: string]: any; } let tom: Person = { id: 89757, name: 'Tom', gender: 'male' };
//tom.id = 9527; //这里不允许给只读属性赋值,否则会报错
注意:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
数组的类型
1.「类型 + 方括号」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5]; let fibonacci2: string[] = ['1', '1', '2', '3', '5']; fibonacci.push('9') //报错,fibonacci不是string类型 fibonacci2.push('9')
2.数组泛型
let fibonacci3: Array<number> = [1, 1, 2, 3, 5]; let fibonacci4: string[] = ['1', '1', '2', '3', '5']; fibonacci3.push('9') //报错,fibonacci3不是string类型 fibonacci4.push('9')
注意:在声明变量时,指定的变量为number,就不允许往其添加其他类型的值
3.用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray
表示:只要索引的类型是数字时,那么值的类型必须是数字
4.any在数组中的运用
let list: any[] = ['xcatliu', 25, { website: 'abc' }];
用any表示数组中允许出现任意类型
5.类数组
...
函数的类型
1.函数声明
function sum(x: number, y: number): number { return x + y; }
注意:输入多余或者少于的参数,是不允许的。
2.函数表达式
let mySum = function (x: number, y: number): number { return x + y; };
如果我们手动给sum添加类型,则应该是:
let mySum: (x: number, y: number) => number = function (x: number, y: number): number { return x + y; };
注意:此处的=>符号和ES6中的=>符号不一样。在 TypeScript 的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。在 ES6 中,=>
叫做箭头函数。
3.用接口定义函数的形状
interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { return source.search(subString) !== -1; }
4.可选参数
与接口中的可选属性类似,我们用 ?
表示可选的参数
function buildName(firstName: string, lastName?: string) { if (lastName) { return firstName + ' ' + lastName; } else { return firstName; } } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
注意:可选参数必须接在必须参数后面,就是说可选参数后面不允许出现必须参数
function buildName(firstName?: string, lastName: string) { if (firstName) { return firstName + ' ' + lastName; } else { return lastName; } } // 报错
5.参数默认值
TypeScript 会将添加了默认值的参数识别为可选参数
function buildName(firstName: string, lastName: string = 'Cat') { return firstName + ' ' + lastName; } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
此时就不受「可选参数必须接在必需参数后面」的限制了
function buildName(firstName: string = 'Tom', lastName: string) { return firstName + ' ' + lastName; } let tomcat = buildName('Tom', 'Cat'); let cat = buildName(undefined, 'Cat');
6.剩余参数
ES6 中,可以使用 ...rest
的方式获取函数中的剩余参数(rest 参数)
function push(array, ...items) { items.forEach(function(item) { array.push(item); }); } let a = []; push(a, 1, 2, 3);
注意:rest参数只能是最后一个参数,关于 rest 参数,可以参考 ES6 中的 rest 参数。
7.重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
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(''); } }
上例中,我们重复定义了多次函数 reverse
,前几次都是函数定义,最后一次是函数实现。
注意:TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
用来手动指定一个值的类型
语法
<类型>值
或
值 as 类型
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。
使用场景
之前有个例子当函数变量使用联合类型,如果我们在函数体中需要特定类型的方法,就需要使用类型断言,不然编译的时候就会报错:
function getLength(something: string | number): number { if ((<string>something).length) { return (<string>something).length; //不加类型断言就会报错 } else { return something.toString().length; } }
注意,类型断言不是类型转换,断言一个联合类型不存在的类型会报错:
function toBoolean(something: string | number): boolean { return <boolean>something; // 报错, 布尔类型不能赋值给something }
内置对象
参考地址:https://ts.xcatliu.com/basics/built-in-objects
类型别名
用来给一个类型起个新名字
type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } } console.log(getName('123456')) //123456
上例中,我们使用 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中的其中一个
注意:类型别名与字符串字面量类型都是使用 type
进行定义。
元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
1.例子
let tom: [string, number] = ['Tom', 25];
//或者
let tom2: [string, number];
tom2[0] = 'Tom';
tom2[1] = 25;
tom2[0].slice(1);
tom2[1].toFixed(2);
//也可以只赋值其中一项
let tom3: [string, number];
tom3 = ['Tom', 25];
2.越界的元素
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型
let tom5: [string, number]; tom5 = ['Tom', 25]; tom5.push('male'); // tom5.push(true); //报错,只能添加上面声明的类型
枚举
枚举类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
1.简单的例子
枚举使用 enum
关键字来定义
枚举成员会被赋值为从 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 = {}));
2.手动赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true
未手动赋值的枚举项会接着上一个枚举项递增
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的:
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 3); // true console.log(Days["Wed"] === 3); // true console.log(Days[3] === "Sun"); // false console.log(Days[3] === "Wed"); // true
上面的例子中,递增到 3
的时候与前面的 Sun
的取值重复了,但是 TypeScript 并没有报错,导致 Days[3]
的值先是 "Sun"
,而后又被 "Wed"
覆盖了。编译的结果是:
var Days; (function (Days) { Days[Days["Sun"] = 3] = "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 = {}));
所以使用的时候需要注意,最好不要出现这种覆盖的情况。
手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的)。
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"}; //编译成 var Days; (function (Days) { Days[Days["Sun"] = 7] = "Sun"; Days[Days["Mon"] = 8] = "Mon"; Days[Days["Tue"] = 9] = "Tue"; Days[Days["Wed"] = 10] = "Wed"; Days[Days["Thu"] = 11] = "Thu"; Days[Days["Fri"] = 12] = "Fri"; Days[Days["Sat"] = "S"] = "Sat"; })(Days || (Days = {}));
手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1。
enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1.5); // true console.log(Days["Tue"] === 2.5); // true console.log(Days["Sat"] === 6.5); // true
3.常数项和计算所得项
枚举项有两种类型:常数项和计算所得项。
前面我们所举的例子都是常数项,一个典型的计算所得项的例子:
enum Color {Red, Green, Blue = "blue".length};
上面的例子中,"blue".length
就是一个计算所得项。
上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
enum Color {Red = "red".length, Green, Blue}; // index.ts(1,33): error TS1061: Enum member must have initializer. // index.ts(1,40): error TS1061: Enum member must have initializer.
4.常数枚举
使用 const enum
定义的枚举类型
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; //打印结果为[0,1,2,3]
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
假如包含了计算成员,则会在编译阶段报错:
const enum Color {Red, Green, Blue = "blue".length}; // index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression
5.外部枚举
使用 declare enum
定义的枚举类型
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
之前提到过,declare
定义的类型只会用于编译时的检查,编译结果中会被删除。
上例的编译结果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
外部枚举与声明语句一样,常出现在声明文件中。
同时使用 declare
和 const
也是可以的:
declare const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
编译结果:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
TypeScript 的枚举类型的概念来源于 C#。
类