原文地址地址:TypeScript 基础精粹 javascript
基础笔记的github地址:https://github.com/qiqihaobenben/Front-End-Basics ,能够watch,也能够star。
java
有两种类型注解方式,特别注意第二种使用 TS 内置的 Array 泛型接口。git
let arr1: number[] = [1,2,3] // 下面就是使用 TS 内置的 Array 泛型接口来实现的 let arr2: Array<number | string> = [1,2,3,"abc"]
元组是一种特殊的数组,限定了数组元素的个数和类型github
let tuple: [number, string] = [0, "1"];
须要注意元组的越界问题,虽然能够越界添加元素,可是仍然是不能越界访问,强烈不建议这么使用typescript
tuple.push(2) // 不报错 console.log(tuple) // [0, "1", 2] 也能都打印出来 console.log(tuple[2]) // 可是想取出元组中的越界元素,就会报错元组长度是2,在index为2时没有元素
函数类型能够先定义再使用,具体实现时就能够不用注明参数和返回值类型了,并且参数名称也不用必须跟定义时相同。数组
let compute: (x: number, y: number) => number; compute = (a, b) => a + b;
对象若是要赋值或者修改属性值,那么就不能用简单的对象类型,须要定义完整的对象类型app
let obj: object = { x: 1, y: 2 }; obj.x = 3; // 会报错,只是简单的定义了是object类型,可是里面到底有什么属性没有标明 // 须要改为以下的对象类型定义 let obj: { x: number; y: number } = { x: 1, y: 2 }; obj.x = 3;
symbol 类型能够直接声明为 symbol 类型,也能够直接赋值,跟 ES6 同样,两个分别声明的 symbol 是不相等的。dom
let s1: symbol = Symbol(); let s2 = Symbol(); console.log(s1 === s2) // false
变量能够被声明为 undefined 和 null ,可是一旦被声明,就不能再赋值其余类型。分布式
let un: undefined = undefined; let nu: null = null; un = 1 // 会报错 nu = 1 // 会报错
undefined 和 null 是任何类型的子类型,那就能够赋值给其余类型。可是须要设置配置项 "strictNullChecks": false函数
// 设置 "strictNullChecks": false let num: number = 123; num = undefined; num = null; // 可是更建议将 num 设置为联合类型 let num: number | undefined | null = 123; num = undefined; num = null;
枚举分为数字枚举和字符串枚举,此外还有异构枚举(不推荐)
枚举既能经过名字取值,又能经过索引取值,咱们具体看一下是怎么取到的。
enum Role { Reporter = 1, Developer, Maintainer, Owner, Guest } Role.Reporter = 2 // 枚举成员是只读的,不能修改从新赋值 console.log(Role) //打印出来:{1: "Reporter", 2: "Developer", 3: "Maintainer", 4: "Owner", 5: "Guest", Reporter: 1, Developer: 2, Maintainer: 3, Owner: 4, Guest: 5} //咱们看到打印出来是一个对象,对象中有索引值做为 key 的,有名字做为 key 的,因此枚举既能经过名字取值,又能经过索引取值 // 看一下 TS 编译器是怎么用反向映射实现枚举的。 "use strict"; var Role; (function (Role) { Role[Role["Reporter"] = 1] = "Reporter"; Role[Role["Developer"] = 2] = "Developer"; Role[Role["Maintainer"] = 3] = "Maintainer"; Role[Role["Owner"] = 4] = "Owner"; Role[Role["Guest"] = 5] = "Guest"; })(Role || (Role = {}));
字符串枚举只能经过名字取值,不能经过索引取值。
enum Message { Success = '成功', Fail = '失败' } console.log(Message) // 打印出来:{Success: "成功", Fail: "失败"} // 咱们看到只有名字做为 key ,说明字符串枚举不能反向映射
用 const 声明的枚举就是常量枚举,会在编译阶段被移除。以下代码编译后 Month 是不产生代码的,只能在编译前使用,当咱们不须要一个对象,可是须要一个对象的值的时候,就可使用常量枚举,这样能够减小编译后的代码。
const enum Month { Jan, Feb, Mar } let month = [Month.Jan, Month.Feb, Month.Mar];
数字和字符串枚举混用,不推荐
enum Answer { N, Y = 'Yes', // C, // 在字符串枚举成员后面的枚举成员必须赋一个初始值 // X = Math.random() // 含字符串成员的枚举中不容许使用计算值 }
枚举成员的分为 const member 和 computer member
数字枚举中,若是有两个成员有一样索引,那么后面索引会覆盖前面的(见下面的枚举 number )
// 枚举成员 enum Char { // const member 常量枚举,会在编译阶段计算结果,以常量的形式出如今运行时环境 a, b = Char.a, c = 1 + 3, // computed member 须要被计算的枚举成员,不会在编译阶段进行计算,会被保留到执行阶段 d = Math.random(), e = '123'.length, // 在 computed member 后面的枚举成员,必定要赋一个初始值,不然报错 f = 1 } console.log(Char) // 枚举 number enum number { a = 1, b = 5, c = 4, d } console.log(number) //打印出{1: "a", 4: "c", 5: "d", a: 1, b: 5, c: 4, d: 5} // b赋初始值为5,c赋初始值为4,按照索引递增,d的索引就是5,索引相同时,后面的值覆盖前面的,因此5对应的 value 就是d
有如下三种状况,(1)枚举成员都没有初始值、(2)枚举成员都是数字枚举、(3)枚举成员都是字符串枚举
enum E { a, b } // 枚举成员都没有初始值 enum F { a = 1, b = 5, c = 4, d } // 枚举成员都是数字枚举 enum G { a = 'apple', b = 'banana' } // 枚举成员都是字符串枚举 // 变量定义为数字枚举类型,赋值任意number类型的值都是能够的,对枚举没有影响,可是不能赋值字符串等。 let e: E = 3 let f: F = 3 // e === f // 不一样的枚举类型是不能比较的,会报错 console.log(E,F,e,f) // 打印:{0: "a", 1: "b", a: 0, b: 1}, {1: "a", 4: "c", 5: "d", a: 1, b: 5, c: 4, d: 5}, 3, 3 // 可见变量定义为E,F赋值,对E,F枚举自己没有影响 let e1: E = 3 let e2: E = 3 console.log(e1 === e2) // 同一个枚举类型是能够比较的,结果为true let e3: E.a = 3 let e4: E.b = 3 // e3 === e4 // 同一个枚举类型的不一样枚举成员是不能比较的,会报错 console.log(E,E.a,E.b,e3,e4) // 打印:{0: "a", 1: "b", a: 0, b: 1} 0 1 3 3 ,可见变量定义为E.a,E.b赋值,对E以及E.a,E.b枚举自己没有影响 //字符串枚举类型的赋值,只能用枚举成员,不能随意赋值。 let g1: G = 'abc' // 会报错 let g2: G = G.a // g2能赋值G.a或者G.b let g3: G.a = G.a // g2 只能赋值G.a
接口约束对象、函数、类的结构
对象类型接口直接验证有冗余字段的对象字面量时会报错,这种冗余字段有时是不可避免的存在的。
interface List { id: number; name: string; } interface Result { data: List[]; } function render(result: Result) { result.data.forEach((value) => { console.log(value.id,value.name) }) } render({ data: [ {id: 1, name: 'A',sex: 'male'}, {id: 2,name: 'B'} ] }); // 这就是对象类型接口直接验证有冗余字段的“对象字面量”,上面render中会有报错,说对象只能指定已知属性,而且"sex"不在类型"List"中
解决方法一:在外面声明变量 result ,而后把 result 传入 render 函数,避免传入对象字面量。
// 把字面量先赋值给一个变量这样就能绕过检测 let result = { data: [ {id: 1, name: 'A',sex: 'male'}, {id: 2,name: 'B'} ] } render(result);
解决方法二: 用类型断言(两种 as 和尖括号),可是若是对象字面中都没有符合的,仍是会报错,能够用 as unknown as xxx
render({ data: [{ id: 1, name: "A", sex: "male" }, { id: 2, name: "B" }] } as Result); // 可是若是传入的对象字面量中没有一项是符合的,那用类型断言仍是会报错 render({ data: [{ id: 1, name: "A", sex: "male" }] } as Result); // 仍是会报错属性"data"的类型不兼容 // 如今就须要这么写,用 as unknown as xxx render({ data: [{ id: 1, name: "A", sex: "male" }] } as unknown as Result);
解决方法三:用字符串索引签名
interface List { id: number; name: string; [x:string]: any; } // 这样对象字面量就能够包含任意多个字符串属性了。
interface List { readonly id: number; // 只读属性 name: string; age?: number; // 可选属性 }
##### 可索引类型
不肯定一个接口中有多少属性时,可使用可索引类型。分为数字索引签名和字符串索引签名,若是接口定义了某一种索引签名的值的类型,以后再定义的属性的值必须是签名值的类型的子类型。能够同时使用两种类型的索引,可是数字索引的返回值必须是字符串索引返回值类型的子类型。
interface Names { [x: string]: number | string; // y: boolean; // 会报错 boolean 不会赋值给字符串索引类型,由于字符串索引签名的类型是 number | string,因此以后再定义的属性必须是签名值类型的子类型 [z: number]: number; // 字符串索引签名后也能定义数字索引签名,数字索引的返回值必须是字符串索引返回值类型的子类型 }
interface Add { (x: number, y: number): number; } // 跟变量声明是等价的:let Add: (a: number, b: number) => number let add4: Add = (a,b) => a + b
混合接口,须要注意看一下,接口中的属性没有顺序之分,混合接口不须要第一个属性是匿名函数。
interface Lib { version: string; ():void; doSomething():void; } // 须要用到类型断言 let lib: Lib = (() => {}) as Lib; lib.version = '1.0' lib.doSomething = () => {}
// 如下是接口继承的例子 interface Human { name: string; eat(): void; } interface Man extends Human { run(): void } interface Child { cry():void } interface Boy extends Man, Child {} let boy: Boy = { name: '', run(){}, eat(){}, cry(){} }
// 第一种,直接声明 function add1 (x:number, y:number):number { return x + y } // 应用时形参和实参一一对应 add1(1, 2) // 第二种 变量声明 let add2: (x:number, y:number) => number // 应用以下 add2 = (a, b) => a + b add2(2, 2) // 第三种 类型别名 type Add3 = (x: number, y: number) => number // 应用以下 let add3: Add3 = (a, b) => a + b add3(3, 2) // 第四种 接口实现 interface Add4 { (x: number, y: number): number; } // 跟变量声明是等价的:let Add4: (a: number, b: number) => number let add4: Add4 = (a,b) => a + b add4(4, 2)
可选参数必须位于必选参数以后,便可选参数后面不能再有必选参数
// y后面不能再有必选参数,因此d会报错 // function add5(x:number, y?:number, d:number) { // 正确以下 function add5(x:number, y?:number) { return y? y + x: x } add5(1)
带默认值的参数不须要放在必选参数后面,但若是带默认值的参数出如今必选参数前面,必须明确的传入 undefined 值来得到默认值。在全部必选参数后面的带默认值的参数都是可选的,与可选参数同样,在调用函数的时候能够省略。
function add6 (x: number, y = 0, z:number,q = 1) { return x +y + z +q } // 第二个参数必须传入undefined占位 add6(1,undefined,2)
要求定义一系列的函数声明,在类型最宽泛的版本中实现重载, TS 编译器的函数重载会去查询一个重载的列表,而且从最开始的一个进行匹配,若是匹配成功,就直接执行。因此咱们要把大几率匹配的定义写在前面。
函数重载的声明只用于类型检查阶段,在编译后会被删除。
function add8(...rest: number[]):number function add8(...rest: string[]):string function add8(...rest: any[]):any { let first = rest[0] if(typeof first === 'string') { return rest.join('') } if(typeof first === 'number') { return rest.reduce((pre,cur) => pre + cur) } } add8(1,2,3) // 6 add8('1','2','3') // '123'
派生类的构造函数必须包含“super”调用,而且访问派生类的构造函数中的this以前,必须调用“super"
一、public: 全部人可见(默认)。
二、 private: 私有属性
私有属性只能在声明的类中访问,在子类或者生成的实例中都不能访问,可是 private 属性能够在实例的方法中被访问到,由于也至关于在类中访问,可是子类的的实例方法确定是访问不到的。
能够把类的 constructor 定义为私有类型,那么这个类既不能被实例化也不能被继承
三、 protected 受保护属性
受保护属性只能在声明的类及其子类中访问,可是 protected 属性能够在实例的方法中被访问到,由于也至关于在类中访问
能够把类的 constructor 定义为受保护类型,那么这个类不能被实例化,可是能够被继承,至关于基类
四、 readonly 只读属性
只读属性必须具备初始值,或者在构造函数中初始化,初始化后就不能更改了,而且已经设置过初始值的只读属性,也是能够在构造函数中被从新初始化的。可是在其子类的构造函数中不能被从新初始化。
五、 static 静态属性
只能经过类的名称调用,不能在实例和构造函数或者子类中的构造函数和实例中访问,可是静态属性是能够继承的,用子类的类名能够访问
注意:构造函数的参数也能够添加修饰符,这样能够将参数直接定义为类的属性
class Dog { constructor(name: string) { this.name = name this.legs = 4 // 已经有默认值的只读属性是能够被从新初始化的 } public name: string run() { } private pri() { } protected pro() { } readonly legs: number = 3 static food: string = 'bones' } let dog = new Dog('jinmao') // dog.pri() // 私有属性不能在实例中调用 // dog.pro() // 受保护的属性,不能在实例中调用 console.log(Dog.food) // 'bones' class Husky extends Dog { constructor(name: string, public color: string) { super(name) this.color = color // this.legs = 5 // 子类的构造函数中是不能对父类的只读属性从新初始化的 // this.pri() // 子类不能调用父类的私有属性 this.pro() // 子类能够调用父类的受保护属性 } protected age: number = 3 private nickname: string = '二哈' info(): string { return this.age + this.nickname } // color: string // 参数用了修饰符,能够直接定义为属性,这里就不须要了 } let husky = new Husky('husky', 'black') husky.info() // 若是调用的类的方法中有对类的私有属性和受保护属性的访问,这是不报错的。 console.log(Husky.food) // 'bones' 子类能够调用父类的静态属性
只能被继承,不能被实例化的类。
在抽象类中能够添加共有的方法,也能够添加抽象方法,而后由子类具体实现
abstract class Animal { eat() { console.log('eat') } abstract sleep(): void // 抽象方法,在子类中实现 } // let animal = new Animal() // 会报错,抽象类没法建立实例 class Cat extends Animal { constructor(public name: string) { super() } run() { } // 必须实现抽象方法 sleep() { console.log('sleep') } } let cat = new Cat('jiafei') cat.eat()
interface Human { // new (name:string):void // 接口不能约束类的构造函数 name: string; eat(): void; } class Asian implements Human { constructor (name: string) { this.name = name } name: string // private name: string // 实现接口时用了私有属性会报错 eat() {} sleep(){} }
至关于把类的成员抽象出来,只有类的成员结构,可是没有具体实现
接口抽离类成员时不只抽离了公有属性,还抽离了私有属性和受保护属性,因此非继承的子类都会报错
被抽象的类的子类,也能够实现类抽象出来的接口,并且不用实现这个子类的父类已有的属性
class Auto { state = 1 // protected state2 = 0 // 下面的C会报错,由于C并非 Auto 的子类,C只是实现了 Auto 抽象出来的接口 } interface AutoInterface extends Auto { } class C implements AutoInterface { state = 1 } // 被抽象的类的子类,也能够实现类抽象出来的接口,并且不用实现父类的已有的属性 class Bus extends Auto implements AutoInterface { // 不用设置 state ,Bus 的父类已经有了。 }
注意:用泛型定义函数类型时的位置不用,决定是否须要指定参数类型,见下面例子。
泛型函数例子
function log<T>(value: T): T { console.log(value) return value } log<string[]>(['a', 'b']) log([1, 2]) // 能够不用指定类型,TS会自动推断 // 还能够用类型别名定义泛型函数 //下面的定义不用指定参数类型 type Log = <T>(value:T) => T // 不用指定参数类型,会本身推断 let myLog: Log = log //下面的定义必须指定参数类型 type Log<T> = (value:T) => T // 若是这样用泛型定义函数类型,必须指定一个参数类型 let myLog: Log<string> = log
function log<T>(value: T): T { console.log(value) return value } // 如下仅约束泛型接口中的一个泛型函数,实现不用指定泛型的参数类型 interface Log { <T>(value: T): T; } let myLog: Log = log // 如下约束整个泛型接口,实现须要指定泛型的参数类型,或者用带默认类型的泛型 interface Log1<T> { (value: T): T; } let myLog1: Log1<string> = log interface Log2<T = string> { (value: T): T } let myLog2: Log2 = log
注意:泛型接口的泛型定义为全局时,实现必须指定一个参数类型,或者用带默认类型的泛型
class Log3<T> { // 静态成员不能引用类的泛型参数 // static start(value: T) { // console.log(value) // } run(value: T) { console.log(value) return value } } let log3 = new Log3<number>() log3.run(1) //不指定类型,就能够传入任何类型 let log4 = new Log3() log4.run('abc')
注意:泛型不能应用于类的静态成员。而且实例化时,不指定类型,就能够传入任何类型
约束泛型传入的类型
interface Length { length: number } function log5<T extends Length>(value: T) { // 想要打印出定义为泛型T的value的length属性,则T必需要有length属性,因此须要泛型约束,T继承length接口后,就确定具备了length属性 console.log(value,value.length) return value } log5([1]) log5('abc') log5({length: 1})
类型检查机制: TypeScript 编译器在作类型检查时,所秉承的一些原则,以及表现出的一些行为。其做用是辅助开发,提升开发效率
类型推断: 指的是不须要指定变量的类型(函数的返回值类型),TypeScript 能够根据某些规则自动地为其推断出一个类型
let a = 1 // 推断为 number let b = [1] // 推断为 number[] let c = (x = 1) => x + 1 // 推断为 (x?: number) => number
当须要从多个类型中推断出一个类型的时候,TypeScript 会尽量的推断出一个兼容当前全部类型的通用类型
let d = [1, null] // 推断为一个最兼容的类型,因此推断为(number | null)[] // 当关闭"strictNullChecks"配置项时,null是number的子类型,因此推断为number[]
以上的推断都是从右向左,即根据表达式推断,上下文类型推断是从左向右,一般会发生在事件处理中。
在肯定本身比 TS 更准确的知道类型时,可使用类型断言来绕过 TS 的检查,改造旧代码颇有效,可是防止滥用。
interface Bar { bar: number } let foo = {} as Bar foo.bar = 1 // 可是推荐变量声明时就要指定类型 let foo1: Bar = { bar: 1 }
当一个类型Y能够被赋值给另外一个类型X时,咱们就能够说类型X兼容类型Y
X兼容Y:X(目标类型) = Y(源类型)
let s: string = 'a' s = null // 把编译配置中的strictNullChecks设置成false,字符类型是兼容null类型的(由于null是字符的子类型)
成员少的兼容成员多的
interface X { a: any; b: any; } interface Y { a: any; b: any; c: any; } let x: X = { a: 1, b: 2 } let y: Y = { a: 1, b: 2, c: 3 } // 源类型只要具备目标类型的必要属性,就能够进行赋值。接口之间相互兼容,成员少的兼容成员多的。 x = y // y = x // 不兼容
type Handler = (a: number, b: number) => void function test(handler: Handler) { return handler }
目标函数的参数个数必定要多于源函数的参数个数
Handler 目标函数,传入 test 的 参数函数 就是源函数
let handler1 = (a: number) => { } test(handler1) // 传入的函数能接收一个参数,且参数是number,是兼容的 let handler2 = (a: number, b: number, c: number) => { } test(handler2) // 会报错 传入的函数能接收三个参数(参数多了),且参数是number,是不兼容的
let a1 = (p1: number, p2: number) => { } let b1 = (p1?: number, p2?: number) => { } let c1 = (...args: number[]) => { }
(1) 固定参数是能够兼容可选参数和剩余参数的
a1 = b1 // 兼容 a1 = c1 // 兼容
(2) 可选参数是不兼容固定参数和剩余参数的,可是能够经过设置"strictFunctionTypes": false来消除报错,实现兼容
b1 = a1 //不兼容 b1 = c1 // 不兼容
(3) 剩余参数能够兼容固定参数和可选参数
c1 = a1 // 兼容 c1 = b1 // 兼容
// 接上面的test函数 let handler3 = (a: string) => { } test(handler3) // 类型不兼容
接口成员多的兼容成员少的,也能够理解把接口展开,参数多的兼容参数少的。对于不兼容的,也能够经过设置"strictFunctionTypes": false来消除报错,实现兼容
interface Point3D { x: number; y: number; z: number; } interface Point2D { x: number; y: number; } let p3d = (point: Point3D) => { } let p2d = (point: Point2D) => { } p3d = p2d // 兼容 p2d = p3d // 不兼容
目标函数的返回值类型必须与源函数的返回值类型相同,或者是其子类型
let f = () => ({ name: 'Alice' }) let g = () => ({ name: 'A', location: 'beijing' }) f = g // 兼容 g = f // 不兼容
函数重载列表(目标函数)
function overload(a: number, b: number): number; function overload(a: string, b: string): string;
函数的具体实现(源函数)
function overload(a: any, b: any): any { }
目标函数的参数要多于源函数的参数才能兼容
function overload(a:any,b:any,c:any):any {} // 具体实现时的参数多于重载列表中匹配到的第一个定义的函数的参数,也就是源函数的参数多于目标函数的参数,不兼容
返回值类型不兼容
function overload(a:any,b:any) {} // 去掉了返回值的any,不兼容
enum Fruit { Apple, Banana } enum Color { Red, Yello }
let fruit: Fruit.Apple = 4 let no: number = Fruit.Apple
let color: Color.Red = Fruit.Apple // 不兼容
和接口比较类似,只比较结构,须要注意,在比较两个类是否兼容时,静态成员和构造函数是不参与比较的,若是两个类具备相同的实例成员,那么他们的实例就相互兼容
class A { constructor(p: number, q: number) { } id: number = 1 } class B { static s = 1 constructor(p: number) { } id: number = 2 } let aa = new A(1, 2) let bb = new B(1) // 两个实例彻底兼容,静态成员和构造函数是不比较的 aa = bb bb = aa
类中存在私有属性状况有两种,若是其中一个类有私有属性,另外一个没有。没有的能够兼容有的,若是两个类都有,那两个类都不兼容。
若是一个类中有私有属性,另外一个类继承了这个类,那么这两个类就是兼容的。
class A { constructor(p: number, q: number) { } id: number = 1 private name:string = '' // 只在A类中加这个私有属性,aa不兼容bb,可是bb兼容aa,若是A、B两个类中都加了私有属性,那么都不兼容 } class B { static s = 1 constructor(p: number) { } id: number = 2 } let aa = new A(1, 2) let bb = new B(1) aa = bb // 不兼容 bb = aa // 兼容 // A中有私有属性,C继承A后,aa和cc是相互兼容的 class C extends A { } let cc = new C(1, 2) // 两个类的实例是兼容的 aa = cc cc = aa
泛型接口为空时,泛型指定不一样的类型,也是兼容的。
interface Empty<T> {} let obj1:Empty<number> = {} let obj2:Empty<string> = {} // 兼容 obj1 = obj2 obj2 = obj1
若是泛型接口中有一个接口成员时,类型不一样就不兼容了
interface Empty<T> { value: T } let obj1:Empty<number> = {} let obj2:Empty<string> = {} // 报错,都不兼容 obj1 = obj2 obj2 = obj1
两个泛型函数若是定义相同,没有指定类型参数的话也是相互兼容的
let log1 = <T>(x: T): T => { return x } let log2 = <U>(y: U): U => { return y } log1 = log2 log2 = log1
指的是 TypeScript 可以在特定的区块(类型保护区块
)中保证变量属于某种特定的类型。能够在此区块中放心地引用此类型的属性,或者调用此类型的方法。
前置代码,以后的代码在此基础运行
enum Type { Strong, Week } class Java { helloJava() { console.log('hello Java') } java: any } class JavaScript { helloJavaScript() { console.log('hello JavaScript') } javaScript: any }
实现 getLanguage 方法直接用 lang.helloJava 是否是存在做为判断是会报错的
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // 若是想根据lang实例的类型,直接用lang.helloJava是否是存在来做为判断是会报错的,由于如今lang是Java和JavaScript这两种类型的联合类型 if (lang.helloJava) { lang.helloJava() } else { lang.helloJavaScript() } return lang }
利用以前的知识可使用类型断言解决
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // 这里就须要用类型断言来告诉TS当前lang实例要是什么类型的 if ((lang as Java).helloJava) { (lang as Java).helloJava() } else { (lang as JavaScript).helloJavaScript() } return lang }
类型保护第一种方法,instanceof
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // instanceof 能够判断实例是属于哪一个类,这样TS就能判断了。 if (lang instanceof Java) { lang.helloJava() } else { lang.helloJavaScript() } return lang }
类型保护第二种方法, in 能够判断某个属性是否是属于某个对象
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // in 能够判断某个属性是否是属于某个对象 如上helloJava和java都能判断出来 if ('java' in lang) { lang.helloJava() } else { lang.helloJavaScript() } return lang }
类型保护第三种方法, typeof 类型保护,能够帮助咱们判断基本类型
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // x也是联合类型,typeof类型保护,能够判断出基本类型。 if (typeof x === 'string') { x.length } else { x.toFixed(2) } return lang }
类型保护第四种方法,经过建立一个类型保护函数来判断对象的类型
类型保护函数的返回值有点不一样,用到了 is ,叫作类型谓词
function isJava(lang: Java | JavaScript): lang is Java { return (lang as Java).helloJava !== undefined }
function getLanguage(type: Type, x: string | number) { let lang = type === Type.Strong ? new Java() : new JavaScript() // 经过建立一个类型保护函数来判断对象的类型 if (isJava(lang)) { lang.helloJava() } else { lang.helloJavaScript() } return lang }
不一样的判断方法有不一样的使用场景:
用 &
符号。虽然叫交叉类型,可是是取的全部类型的并集。
interface DogInterface { run(): void } interface CatInterface { jump(): void } // 交叉类型 用 & 符号。虽然叫交叉类型,可是是取的全部类型的并集。 let pet: DogInterface & CatInterface = { run() { }, jump() { } }
声明的类型并不肯定,能够为多个类型中的一个,除了能够是 TS 中规定的类型外,还有字符串字面量联合类型、数字字面量联合类型
let a: number | string = 1; // 字符串字面量联合类型 let b: 'a' | 'b' | 'c' = 'a' // 数字字面量联合类型 let c: 1 | 2 | 3 = 1
对象的联合类型,只能取二者共有的属性,因此说对象联合类型只能访问全部类型的交集
// 接上文DogInterface和CatInterface class Dog implements DogInterface { run() { } eat() { } } class Cat implements CatInterface { jump() { } eat() { } } enum Master { Boy, Girl } function getPet(master: Master) { // pet为Dog和Cat的联合类型,只能取二者共有的属性,因此说联合类型在此时只能访问全部类型的交集 let pet = master === Master.Boy ? new Dog() : new Cat() pet.eat() // pet.run() // 不能访问,会报错 return pet }
这种模式是结合了联合类型和字面量类型的类型保护方法,一个类型若是是多个类型的联合类型,而且每一个类型之间有一个公共的属性,咱们就能够凭借这个公共属性来建立不一样的类型保护区块。
核心是利用两种或多种类型的共有属性,来建立不一样的代码保护区块
下面的函数若是只有 Square 和 Rectangle 这两种联合类型,没有问题,可是一旦扩展增长 Circle 类型,类型校验就不会正常运行,并且也不报错,这个时候咱们是但愿代码有报错提醒的。
interface Square { kind: "square"; size: number; } interface Rectangle { kind: 'rectangle'; width: number; height: number; } interface Circle { kind: 'circle'; r: number; } type Shape = Square | Rectangle | Circle // 下面的函数若是只有Square和Rectangle这两种联合类型,没有问题,可是一旦扩展增长Circle类型,不会正常运行,并且也不报错,这个时候咱们是但愿代码有报错提醒的。 function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; break; case "rectangle": return s.width * s.height; break; } } console.log(area({ kind: 'circle', r: 1 })) // undefined,不报错,这个时候咱们是但愿代码有报错提醒的
若是想要获得正确的报错提醒,第一种方法是设置明确的返回值,第二种方法是利用 never 类型.
第一种方法是设置明确的返回值
// 会报错:函数缺乏结束返回语句,返回类型不包括 "undefined" function area(s: Shape): number { switch (s.kind) { case "square": return s.size * s.size; break; case "rectangle": return s.width * s.height; break; } }
第二种方法是利用never类型,原理是在最后default判断分支写一个函数,设置参数是never类型,而后把最外面函数的参数传进去,正常状况下是不会执行到default分支的。
function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; break; case "rectangle": return s.width * s.height; break; case "circle": return Math.PI * s.r ** 2; break; default: return ((e: never) => { throw new Error(e) })(s) //这个函数就是用来检查s是不是never类型,若是s是never类型,说明前面的分支所有覆盖了,若是s不是never类型,说明前面的分支有遗漏,就得须要补一下。 } }
keyof T
表示类型T的全部公共属性的字面量的联合类型
interface Obj { a: number; b: string; } let key: keyof Obj // key的类型就是Obj的属性a和b的联合类型:let key: "a" | "b"
T[K]
表示对象T的属性K所表明的类型
interface Obj { a: number; b: string; } let value: Obj['a'] // value的类型就是Obj的属性a的类型: let value: number
T extends U
泛型变量能够继承某个类型得到某些属性
先看以下代码片断存在的问题。
let obj = { a: 1, b: 2, c: 3 } //以下函数若是访问obj中不存在的属性也是没有报错的。 function getValues(obj: any, keys: string[]) { return keys.map(key => obj[key]) } console.log(getValues(obj, ['a', 'b'])) console.log(getValues(obj, ['e', 'f'])) // 会显示[undefined, undefined],可是TS编译器并无报错。
解决以下
function getValuest<T, K extends keyof T>(obj: T, keys: K[]): T[K][] { return keys.map(key => obj[key]) } console.log(getValuest(obj, ['a', 'b'])) // console.log(getValuest(obj, ['e', 'f'])) // 这样就会报错了
能够从一个旧的类型,生成一个新的类型
如下代码用到了TS内置的映射类型
interface Obj { a: string; b: number; c: boolean } // 如下三种类型称为同态,只会做用于Obj的属性,不会引入新的属性 //把一个接口的全部属性变成只读 type ReadonlyObj = Readonly<Obj> //把一个接口的全部属性变成可选 type PartialObj = Partial<Obj> //能够抽取接口的子集 type PickObj = Pick<Obj, 'a' | 'b'> // 非同态 会建立新的属性 type RecordObj = Record<'x' | 'y', Obj> // 建立一个新的类型并引入指定的新的类型为 // { // x: Obj; // y: Obj; // }
T extends U ? X : Y
type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object" type T1 = TypeName<string> // 获得的类型即: type T1 = "string" type T2 = TypeName<string[]> // 获得的类型即:type T2 = "object"
(A | B) extends U ? X : Y
等价于 (A extends U ? X : Y) | (B extends U ? X : Y)
// 接上文 type T3 = TypeName<string | string[]> // 获得的类型即:type T3 = "string" | "object"
用法一:利用分布式条件类型能够实现 Diff 操做
type T4 = Diff<"a" | "b" | "c", "a" | "e"> // 即:type T4 = "b" | "c" // 拆分一下具体步骤 // Diff<"a","a" | "e"> | Diff<"b","a" | "e"> | Diff<"c", "a" | "e"> // 分布结果以下:never | "b" | "c" // 最终得到字面量的联合类型 "b" | "c"
用法二:在Diff的基础上实现过滤掉 null 和 undefined 的值。
type NotNull<T> = Diff<T, undefined | null> type T5 = NotNull<string | number | undefined | null> // 即:type T5 = string | number
以上的类型别名在TS的类库中都有内置的类型
Diff => Exclude<T, U>
NotNull => NonNullable<T>
此外,内置的还有不少类型,好比从类型T中抽取出能够赋值给U的类型 Extract<T, U>
type T6 = Extract<"a" | "b" | "c", "a" | "e"> // 即:type T6 = "a"
好比: 用于提取函数类型的返回值类型 ReturnType<T>
先写出 ReturnType<T>
的实现,infer
表示在 extends 条件语句中待推断的类型变量。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
分析一下上面的代码,首先要求传入 ReturnType 的 T 必须能赋值给一个最宽泛的函数,以后判断 T 能不能赋值给一个能够接受任意参数的返回值待推断为 R 的函数,若是能够,返回待推断返回值 R ,若是不能够,返回 any 。
type T7 = ReturnType<() => string> //即:type T7 = string