TypeScript
基本类型
- string 字符串
- boolean 布尔
- number 数值
- null
- undefined
- void
- any 任意
- 与void的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量。
- 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。
类型推论
定义的时候有赋值,将会推断成当前值的类型。
let myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查。
let myFavoriteNumber; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
联合类型
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; console.log(myFavoriteNumber.length); // 5 myFavoriteNumber = 7; console.log(myFavoriteNumber.length); // 编译时报错 // index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
上例中,第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。
而第四行的myFavoriteNumber被推断成了number,访问它的 length 属性时就报错了。
接口
任意属性和只读属性
- 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
- 只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
interface Person { readonly id: number; //只读属性 name: string; age?: number; // 可选属性的类型必须是任意属性类型的子集 [propName: string]: string; // 定义任意属性 } let tom: Person = { id: 89757, // 只读属性需要在此时定义 name: 'Tom', age: 25, gender: 'male' }; tom.id \= 89757; // 只读属性不能在此时定义,会报错。 // index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'. // index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'. // Index signatures are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'. // index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'. // Property 'id' is missing in type '{ name: string; gender: string; }'. // index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
数组类型
- 「类型 + 方括号」表示法:
let fibonacci: number[] = [1, 1, 2, 3, 5];
- 数组泛型:“let fibonacci: Array<number> = [1, 1, 2, 3, 5];
- 用接口表示数组:
interface NumberArray { [index: number]: number; }
- 类数组
function sum() { let args: { [index: number]: number; length: number; callee: Function; } = arguments; }
- any类型:
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
函数类型
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(''); } }
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
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 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
语法:值 as 类型
或<类型>值
代码中使用any类型,我们也可以选择改进它,通过类型断言及时的把 any断言为精确的类型,亡羊补牢,使我们的代码向着高可维护性的目标发展。
function getCacheData(key: string): any { // 函数返回any return (window as any).cache[key]; } interface Cat { name: string; run(): void; } const tom = getCacheData('tom') as Cat; // 在使用的时候将其断言成具体类型。 tom.run();
总结:
- 联合类型可以被断言为其中一个类型
- 父类可以被断言为子类
- 任何类型都可以被断言为 any
- any 可以被断言为任何类型
- 要使得
A
能够被断言为B
,只需要A
兼容B
或B
兼容A
即可
双重断言
as any as Foo
interface Cat { run(): void; } interface Fish { swim(): void; } function testCat(cat: Cat) { return (cat as any as Fish); // 双重断言 }
除非迫不得已,千万别用双重断言。
类型断言 vs 类型转换
类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除:
function toBoolean(something: any): boolean { return something as boolean; } toBoolean(1); // 返回值为 1
function toBoolean(something: any): boolean { return Boolean(something); } toBoolean(1); // 返回值为 true
类型断言 vs 类型声明
为了增加代码的质量,我们最好优先使用类型声明,这也比类型断言的 as
语法更加优雅。
interface Animal { name: string; } interface Cat { name: string; run(): void; } const animal: Animal = { name: 'tom' }; let tom = animal as Cat; let tom: Cat = animal; // 会报错 // error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'
-
animal
断言为Cat
,只需要满足Animal
兼容Cat
或Cat
兼容Animal
即可 -
animal
赋值给tom
,需要满足Cat
兼容Animal
才行
类型断言 vs 范型
function getCacheData<T>(key: string): T { return (window as any).cache[key]; } interface Cat { name: string; run(): void; } const tom = getCacheData<Cat>('tom'); tom.run();
通过给 getCacheData
函数添加了一个范型 <T>
,我们可以更加规范的实现对 getCacheData
返回值的约束,这也同时去除掉了代码中的 any
,是最优的一个解决方案。
进阶
类型别名
类型别名常用于联合类型。
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 EventNames = 'click' | 'scroll' | 'mousemove'; function handleEvent(ele: Element, event: EventNames) { // do something } handleEvent(document.getElementById('hello'), 'scroll'); // 没问题 handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick' // index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
元祖
定义一对值分别为 string
和 number
的元组:
let tom: [string, number]; tom[0] = 'Tom'; tom[1] = 25; tom[0].slice(1); tom[1].toFixed(2);
但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
let tom: [string, number]; tom = ['Tom']; // Property '1' is missing in type '[string]' but required in type '[string, number]'.
类
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过
new
生成 - 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如
Cat
和Dog
都继承自Animal
,但是分别实现了自己的eat
方法。此时针对某一个实例,我们无需了解它是Cat
还是Dog
,就可以直接调用eat
方法,程序会自动判断出来应该如何执行eat
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如
public
表示公有属性或方法 - 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
类实现接口
interface Alarm { alert(): void; } 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(): void; } interface Light { lightOn(): void; lightOff(): void; } 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(): void; } interface LightableAlarm extends Alarm { lightOn(): void; lightOff(): void; }
接口继承类
常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript
中却是可以的:
class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
为什么 TypeScript 会支持接口继承类呢?
实际上,当我们在声明 class Point
时,除了会创建一个名为 Point
的类之外,同时也创建了一个名为 Point
的类型(实例的类型),所以我们既可以将 Point
当做一个类来用(使用 new Point
创建它的实例),也可以将 Point
当做一个类型来用(使用 : Point
表示参数的类型):
class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } function printPoint(p: Point) { // 当作参数的类型 console.log(p.x, p.y); } printPoint(new Point(1, 2)); // 当作类,创建实例
值得注意的是,PointInstanceType
相比于 Point
,缺少了 constructor
方法,这是因为声明 Point
类时创建的 Point
类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。
个人理解:接口相当于类产生的类型
class Point { /** 静态属性,坐标系原点 */ static origin = new Point(0, 0); /** 静态方法,计算与原点距离 */ static distanceToOrigin(p: Point) { return Math.sqrt(p.x * p.x + p.y * p.y); } /** 实例属性,x 轴的值 */ x: number; /** 实例属性,y 轴的值 */ y: number; /** 构造函数 */ constructor(x: number, y: number) { this.x = x; this.y = y; } /** 实例方法,打印此点 */ printPoint() { console.log(this.x, this.y); } } interface PointInstanceType { x: number; y: number; printPoint(): void; } let p1: Point; let p2: PointInstanceType;
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
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; } // index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
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<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; }
原文地址:https://segmentfault.com/a/1190000022376321
相关推荐
-
JQuery 中的小技巧 javascript/jquery
2019-8-20
-
Cookie 知识二则 javascript/jquery
2019-3-6
-
ES6+好用的小技巧,让你的代码更干净,短巧,易读 javascript/jquery
2019-2-3
-
【小程序】01 小程序学习遇到的问题汇总 javascript/jquery
2020-7-2
-
小蝌蚪传记:200行代码实现前端无痕埋点 javascript/jquery
2019-11-1
-
iOS11.3以下modal中input光标错位 javascript/jquery
2018-12-12
-
记录一下react脚手架+redux+antd v4 实现登录鉴权,多级路由的学习过程 javascript/jquery
2020-6-11
-
探秘阿里聚石塔技术架构实现(一) javascript/jquery
2020-6-12
-
深入理解令牌认证机制(token) javascript/jquery
2019-3-31
-
让你的组件千变万化,Vue slot 剖玄析微 javascript/jquery
2020-6-12