typescript现在已经慢慢变成前端工程师必学必会的技能了,它在开发大型应用的时候可以让产品更加可控,本篇就试着理出typescript一些必须要掌握的知识点。
原始类型
js有五种基本类型 string、number、boolean、null、undefined,这几种类型typescript都赋予了对应的类型限定,如下:
-
布尔值
let bool: boolean = false
注意
利用Boolean创造的对象不是布尔值:
typescript现在已经慢慢变成前端工程师必学必会的技能了,它在开发大型应用的时候可以让产品更加可控,本篇就试着理出typescript一些必须要掌握的知识点。
原始类型
js有五种基本类型 string、number、boolean、null、undefined,这几种类型typescript都赋予了对应的类型限定,如下:
-
布尔值
bool: boolean = false
注意`利用Boolean创造的对象不是布尔值:
let boolObject: boolean = new Boolean(1) //会报错
new Boolean创造的是一个对象并不是布尔值,
直接调用Boolean倒是可以
let bool: boolean = Boolean(1)
-
字符串
str: string = 'hellow' // 模板字符串 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."; ```
-
数值
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;
其中
0b1010
和0o744
是 ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。 -
null和undefined
null和undefined在typescript中分别是两个独立的类型限定:
let u: undefined = undefined; let n: null = null;
undefined限定的变量只能赋值undefined,null也一样。
-
空值void
void限定的变量只能赋值null或者undefined:let noContent: void = null;
与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: string = 'seven'; myFavoriteNumber = 7; let anyThing: any = 'Tom'; anyThing.setName('Jerry'); anyThing.setName('Jerry').sayHello(); anyThing.myName.setFirstName('Cat');
注意:
没有指定类型的变量,默认就是any类型
。
类型推论
当变量被赋值的时候没有被指定类型限定,会根据变量类型默认设置类型限定:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// 等价于下边
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
联合类型
表示取值为多种类型中的一个
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// 只能赋值string或者number
myFavoriteNumber = true // 会报错
访问联合类型的属性和方法
联合类型的变量只能访问类型共有的属性和方法
function getLength(something: string | number): number {
return something.length; // 会报错,字符串不含有length属性
//想解决报错可以使用类型断言
return <string>something.length // 这样就不会报错
}
// 联合类型当赋值为一个类型会推断赋值为那个类型
myFavoriteNumber = 'ss' // 变量被设定为字符串
myFavoriteNumber.slice(0) // ok
myFavoriteNumber = 5 // 变量被推断为number类型
myFavoriteNumber.slice(0) // 会报错
接口
接口
是面向对象中的概念,表示行为的抽象,具体如何行动通过类实现,在typescript中本人觉得接口主要是起到对编码中的一种约束,可以减少很多错误,并且配合很多ide(比如webstorm)代码提示可以提高编码效率。
简单例子
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
}; // ok
let lilei: Person = {
name: 'Tom',
age: 25,
sexy: 'name'
} // 错误 sexy属性并没有定义
可选属性
上边例子中定义的Person接口限定的变量,必须同时设置name和age两个属性,不然会报错:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
}; // error 缺少 age属性
如果要解决这个,就需要设置可选属性,在变量前边加上?
:
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};//ok
任意属性
有时候我们希望一个接口准许有任意属性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}; // ok
使用 [propName: string]
定义了任意属性取 string
类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
上边例子中[propName: string]: string;
将整个接口设置;属性名为string类型,属性值为string类型,age值为number不符合这个类型,所以报错。
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性,只读属性有两个约束:
对象创建时候要赋值
赋值后不能再重新修改属性值了
例子:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}; // 报错 没有给id赋值
tom.id = 89757; // id是只读,不能修改
数组类型
数组类型定义方式
类型 + 方括号
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push(true) // 报错 不是number类型
数组范型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
接口表示
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
函数类型
函数声明
function sum(x: number, y: number): number {
return x + y;
}
// 表达式声明方式
let mySum = function (x: number, y: number): number {
return x + y;
};
Typescript添加了对函数变量和返回值类型的限定
接口定义函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let 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')
注意:可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了:
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
} // 报错
参数默认值
在 ES6 中,我们允许给函数的参数添加默认值,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');
剩余参数
ES6 中,可以使用 ...rest
的方式获取函数中的剩余参数(rest 参数):
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
方式
<类型>值
或
值 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
}
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个
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'
元组
元组可以理解为存储不同类型值的数组
let tulpe: [string, number] = ['tom', 25];
//或者
let tulpe: [string, number];
tulpe[0] = 'tom';
tulpe[1] = 25;
tulpe[0].slice(1);
tulpe[1].toFixed(2);
//也可以只赋值其中一项
let tulpe: [string, number];
tulpe[0] = 'tom';
tulpe.push(true);// 报错 赋值类型不存在申明类型中
元组使用上需要特别注意的是:
元组初始化的时候必须赋值元组声明的所有类型变量:
let tulpe: [string, number] = ['tom']; // 报错
// 或者
let tulpe: [string,number];
tulpe = ['tome'] // 报错
枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
典型例子
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, 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
未手动赋值的枚举类型变量会自动赋值(在前边变量值逐步累加1)
值得注意的是:如果未手动赋值的枚举项与手动赋值的重复了,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
常数项和计算所得项
枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
前面我们所举的例子都是常数项,一个典型的计算所得项的例子:
enum Color {Red, Green, Blue = "blue".length};
上面的例子中,"blue".length
就是一个计算所得项。
上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
enum Color {Red = "red".length, Green, Blue};
常数枚举
常数枚举是使用 const enum
定义的枚举类型:
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 */];
外部枚举
外部枚举(Ambient Enums)是使用 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];
类
typescript中类增加的内容
-
限定符
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是
public
、private
和protected
。public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问的
抽象类
abstract class Animal { // abstract标志类是抽象类
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack'); // 报错 抽象类不能实例化
抽象类正确的使用方式:
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() { // 实现抽象类中的方法
console.log(`Meow, My name is ${this.name}`);
}
}
let cat = new Cat('Tom');
类的类型
类也可以作为一个类型来使用
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
类与接口
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 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 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};
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
典型的例子
首先,我们来实现一个函数 createArray
,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
上例中,我们在函数名后添加了 <T>
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array<T>
中即可使用了。
接着在调用的时候,可以指定它具体的类型为 string
。当然,也可以不手动指定,而让类型推论自动推算出来:
多个类型参数
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
上例中,泛型 T
不一定包含属性 length
,所以编译的时候报错了。
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length
属性的变量。这就是泛型约束:
nterface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
多个类型参数之间也可以互相约束:
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id]; // 如果不使用泛型约束,这里会报错
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
上例中,我们使用了两个类型参数,其中要求 T
继承 U
,这样就保证了 U
上不会出现 T
中不存在的字段。
泛型接口
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>;
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']
注意,此时在使用泛型接口的时候,需要定义泛型的类型。
泛型类
与泛型接口类似,泛型也可以用于类的类型定义中:
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; };
泛型参数的默认类型
在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型:
函数的合并
我们可以使用重载定义多个函数类型:
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('');
}
}
接口的合并
接口中的属性在合并时会简单的合并到一个接口中:
interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
相当于:
interface Alarm {
price: number;
weight: number;
}
注意,合并的属性的类型必须是唯一的:
interface Alarm {
price: number;
}
interface Alarm {
price: number; // 虽然重复了,但是类型都是 `number`,所以不会报错
weight: number;
}
interface Alarm {
price: number;
}
interface Alarm {
price: string; // 类型不一致,会报错
weight: number;
}
// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'.
接口中方法的合并,与函数的合并一样:
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;
}