typescript 类型系统
对于前端来说,最大的优势其实就是TS的类型系统。
参考:https://www.tslang.cn/docs/handbook/basic-types.html【官网】 或 http://ts.xcatliu.com/basics/index.html(推荐)
一、基础类型(原始类型):
- 布尔值:布尔值是最基础的数据类型,在 TypeScript 中,使用
boolean
定义布尔值类型let isDone: boolean = false;
- 数字:使用
number
定义数值类型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;
- 字符串:使用
string
定义字符串类型let myName: string = 'Tom'; let myAge: number = 25; // 模板字符串 let sentence: string = `Hello`;
- 数组:
let list: number[] = [1, 2, 3];
或,使用数组泛型,
Array<元素类型>
:let list: Array<number> = [1, 2, 3];
- 元组 Tuple:元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// Declare a tuple type let x: [string, number]; // Initialize it x = ['hello', 10]; // OK // Initialize it incorrectly x = [10, 'hello']; // Error
- 枚举:使用枚举类型可以为一组数值赋予友好的名字。【就是字典数据】
enum Color {Red, Green, Blue} // 默认情况下,从0开始为元素编号。即 {Red = 0, Green = 1, Blue = 2} let c: Color = Color.Green;
或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4} let c: Color = Color.Green;
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。即反过来 通过 值 获取 名字。这一点是我们使用字典对象,常用的功能。
enum Color {Red = 1, Green, Blue} let colorName: string = Color[2]; console.log(colorName); // 显示'Green'因为上面代码里它的值是2
- Void:它表示没有任何类型,当一个函数没有返回值时,你通常会见到其返回值类型是
void
function warnUser(): void { console.log("This is my warning message"); }
声明一个
void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null。所以,void就是声明函数没有返回值。
let unusable: void = undefined;
- Null 和 Undefined:在 TypeScript 中,可以使用
null
和undefined
来定义这两个原始数据类型let u: undefined = undefined; let n: null = null;
与
void
的区别是,undefined
和null
是所有类型的子类型。也就是说undefined
类型的变量,可以赋值给number
类型的变量:// 这样不会报错 let num: number = undefined;
而
void
类型的变量不能赋值给number
类型的变量:let u: void; let num: number = u; // Type 'void' is not assignable to type 'number'.
二、泛型 http://ts.xcatliu.com/advanced/generics.html (这里简单介绍,详细看下面的 进阶篇)
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
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 的默认类型是 string 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; }
- 使用时传入的泛型参数,类似函数传入的参数。
三、类型推断 http://ts.xcatliu.com/basics/type-inference.html
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。所以ts中有的变量没有写明类型。
四、联合类型 http://ts.xcatliu.com/basics/union-types.html
联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
-
访问联合类型的属性或方法
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
五、对象的类型--接口 http://ts.xcatliu.com/basics/type-of-object-interfaces.html
在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码 定义契约
- 可选属性:接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): {color: string; area: number} { let newSquare = {color: "white", area: 100}; if (config.color) { newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } let mySquare = createSquare({color: "black"});
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个
?
符号。 - 只读属性:一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用
readonly
来指定只读属性。interface Point { readonly x: number; readonly y: number; } let p1: Point = { x: 10, y: 20 }; p1.x = 5; // error!
TypeScript具有
ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; ro[0] = 12; // error! ro.push(5); // error! ro.length = 100; // error! a = ro; // error!
- 任意属性:一个接口可能需要它除了具有我们需要的属性以为,还可以包含任意的其他属性,这时就要用到任意属性
interface Person { name: string; age?: number; // 这种方式也叫 字符串索引签名 [propName: string]: number | string; } let tom: Person = { name: 'Tommy', addr: '北京' }; console.log(tom);
- 函数类型:接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
interface SearchFunc { (source: string, subString: string): boolean; }
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); return result > -1; }
- 可索引属性
interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0];
- 类 接口
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
- 泛型接口: 查看 下面 泛型的介绍
- 接口继承接口
接口的继承 可以 实现接口 的复用 - 接口继承类
- 类实现接口
总结:一个接口可以作为另一个接口属性的类型,这样就可以给一个多层级对象进行类型声明了。如,
// 接口返回列表数据,每个列表的接口类型 export interface CategoryObj { id: number | string name: string category1Id?: number category2Id?: number } // 接口完整的 返回数据对应的接口类型 export interface CategoryResponseData { data: CategoryObj[], code: number message: string ok: boolean }
六、类型别名:type 关键字 http://ts.xcatliu.com/advanced/type-aliases.html
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值。如,联合类型,元组以及其它任何你需要手写的类型。
type Name = string; // 给原始类型起别名通常没什么用 type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型。 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
重点:
-
类型别名 和 接口 的区别:和接口一样,用来描述对象或函数的类型。一般我们推荐使用 interface,但有些情况还是用 type 方便。
- 类型别名用的地方:无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
- 可以声明联合类型,如 type paramType = number | string;
个人:就一个联合类型,用 接口 定义 感觉太重,用 别名刚刚好。定义好后,其他地方都可以使用。 - 可以声明元组类型,如type arrType = [string, string, number]
- 定义对象时严谨的来说,type 是引用,而 interface是定义。
- 可以声明联合类型,如 type paramType = number | string;
七、数组的类型 http://ts.xcatliu.com/basics/type-of-array.html
八、函数的类型 http://ts.xcatliu.com/basics/type-of-function.html
-
为函数定义类型
// 声明式 函数 function add(x: number, y: number): number { return x + y; } // 表达式 函数 let myAdd = function(x: number, y: number): number { return x + y; };
九、promise对象的类型
- 返回值基础类型
// 声明一个 Promise,成功时返回一个 number 类型的值 let promise: Promise<number>;
- 返回对象
interface backResult{ code: number, data: { name:string,age:number}[], //数组里面的对象类型,这里使用的是类型 message:string } // 在这里声明出promise的类型,使用的接口 let p: Promise<backResult> = new Promise((resolve,reject)=> { resolve({ code: 200, data: [ {name:'张三',age:123} ], message:'操作成功' }) }) p.then((res) => { if (res.code == 200) { let arr = res.data.map(v => v.name) } })
个人:这里promise泛型来表示返回值感觉和函数中的泛型感觉是不一样的。函数中的泛型就是类型变量,而promise的泛型表示的就是返回值的类型。
十、类型断言 http://ts.xcatliu.com/basics/type-assertion.html
型断言(Type Assertion)可以用来手动指定一个值的类型。
进阶篇
十一、泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型。
简单来说:泛型就是一个类型占位符(一个不确定的类型),其它地方也会 根据这个类型 设定 相应的类型。
语法:尖括号表示定义了若干个泛型变量,只有定义了这个变量,后面才能使用它。使用的时候也需要 写明 泛型变量类型,因为ts有类型推断功能,有的情况在使用泛型时就不需要再写明数据类型了。
function identity<T>(arg: T): T { return arg; }
- 泛型函数
function fn<T>(a:T): T{ return a; } // 可以直接调用具有泛型的函数 fn(‘hello’) // 不指定泛型,TS 可以自动对类型进行推断 fn<string>('hell0') // 指定泛型
表达式函数写法
const printFun = <T>(value: T): T => { console.log(value); return value; }; printFun(233);
- 泛型接口
interface Person<J, K> { name: J; age: K; } // 这里使用 泛型接口时,需要写明此时 泛型对应的 数据类型 const me: Person<string, number> = { name: "sunny", age: 18, };
泛型接口中,泛型接口有类似函数。在使用的时候需要写明使用时 泛型 变量对应的 数据类型。