理想论坛_专业20年的财经股票炒股论坛交流社区 - 股票论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2799|回复: 0

TypeScript躬行记(6)——高级类型

[复制链接]

9650

主题

9650

帖子

2万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
28966
发表于 2019-12-27 13:16 | 显示全部楼层 |阅读模式
  本节将对TypeScript中典范的高级特征做具体讲授,包含交织典范、典范别名、典范保护等。
一、交织典范

  交织典范(Intersection Type)是将多个典范经过“&”标记合并成一个新典范,新典范将包含全数典范的特征。例若有Person和Programmer两个类(以下代码所示),当man变量的典范声明为Person&Programmer时,它就能操纵两个类的成员:name属性和work()方式。
  1. class Person {  name: string;}class Programmer {  work() { }}let man: Person&Programmer;man.name;man.work();
复制代码
  交织典范常用于混入(mixin)或此外不适当典型面向工具模子的场景,例如鄙人面的示例中,经过交织典范让新工具obj同时包含a和b两个属性。
  1. function extend(first: T, second: U): T & U {  const result = {};  for (let prop in first) {    (result)[prop] = first[prop];  }  for (let prop in second) {    if (!result.hasOwnProperty(prop)) {      (<U>result)[prop] = second[prop];    }  }  return result;}let obj = extend({ a: 1 }, { b: 2 });
复制代码

二、典范别名

  TypeScript供给了type关键字,用于建立典范别名,可感化于底子典范、团结典范、交织典范和泛型等尽情典范,以下所示。
  1. type Name = string;                //底子典范type Func = () => string;          //函数type Union = Name | Func;          //团结典范type Tuple = [number, number];     //元组type Generic = { value: T };    //泛型
复制代码
  留意,起别名不是新建一个典范,而是供给一个可读性更高的称号。典范别名可在属性里援用本身,但不能出现在声明的右侧,以下所示。
  1. type Tree = {  value: T;  left: Tree;  right: Tree;}type Arrs = Array;            //毛病
复制代码

三、典范保护

  当操纵团结典范时,只能拜候它们的公共成员。假定有一个func()函数,它的参数是由Person和Programmer两个类组成的团结典范,以下代码所示。
  1. function func(man: Person | Programmer) {  if((man).run) {    (man).run();  }else {    (man).work();  }}
复制代码
  固然操纵典范断言可以肯定参数典范,在编译阶段禁止了报错,可是屡次挪用典范断言不免过于烦琐。因而TypeScript就引入了典范保护机制,替换典范断言。典范保护(Type Guard)是一些表达式,答应在运转时检查典范,缩小典范范围。
1)typeof
  TypeScript可将typeof运算符识别成典范保护,从而就能间接在代码里检查典范(以下所示),其盘算结果是个字符串,包含“number”、“string”、“boolean”或“symbol”等关键字。
  1. function send(data: number | string) {  if (typeof data === "number") {    //...  } else if(typeof data === "string") {    //...  }}
复制代码
2)instanceof
  TypeScript也可将instanceof运算符识别成典范保护,经过机关函数来细化典范,检测实例和类能否有关联,以下所示。
  1. function work(man: Person | Programmer) {  if (man instanceof Person) {    //...  } else if(man instanceof Programmer) {    //...  }}
复制代码
3)自界说
  TypeScript还答应自界说典范保护,其形式和函数声明类似,只是返回典范需要改成典范谓词,以下所示。
  1. function isPerson(man: Person | Programmer): man is Person {  return !!(man).run;}
复制代码
  典范谓词由当前函数的参数称号、is关键字和指定的典范称号所组成。
四、字面量典范

  TypeScript可将字符串字面量作为一个典范,用于指定一个字符串典范的牢固值。当该典范与团结典范、典范别名等特征配合操纵时,可以模拟出罗列的结果,以下所示。
  1. type Direction = "Up" | "Down" | "Left";function move(data: Direction) {  return data;}move("Up");           //切确move("Right");        //毛病
复制代码
  move()函数只能吸收Direction典范的三个牢固值,传入此外值城市发生毛病。
  字符串字面量典范还可以用来区分函数重载,以下所示。
  1. function run(data: "Left"): string;function run(data: "Down"): string;function run(data: string) {  return data;}
复制代码
  此外常见的字面量典范还稀有字和布尔值,以下所示。
  1. type Numbers = 1 | 2 | 3 | 4 | 5 | 6;type Bools = true | false;
复制代码
  留意,字面量典范属于单例典范。单例典范是一种只要一个值的典范,当每个罗列成员都用字面量初始化时,罗列成员是具有典范的,叫罗列成员典范,它也属于单例典范。
五、可辨析团结

  经过合并单例典范、团结典范、典范保护和典范别名可建立一种高级形式:可辨析团结(Discriminated Union),也叫做标签团结或代数数据典范。TypeScript中的可辨析团结具有3个要素:
  (1)具有单例典范的属性,即可辨析的特征或标签。
  (2)一个团结了多个典范的典范别名。
  (3)针对第一个要素中的属性的典范保护。
  鄙人面的示例中,首先声大白两个接口,每个接口都有字符串字面量典范的kind属性,而且其值都差别,而kind属性就是第一个要素中的可辨析的特征或标签。
  1. interface Rectangle {  kind: "rectangle";  width: number;  height: number;}interface Circle {  kind: "circle";  radius: number;}
复制代码
  然后将两个接口团结,并建立一个典范别名,实现第二个要素,以下所示。
  1. type Shape = Rectangle | Circle;
复制代码
  末端经过具有判定性的kind属性,结合switch语句,实行典范保护,缩小典范范围,以下所示。
  1. function caculate(s: Shape) {  switch (s.kind) {    case "rectangle":      return s.height * s.width;    case "circle":      return Math.PI * s.radius ** 2;  }}
复制代码
1)完整性检查
  当未涵盖可辨析团结的全数变革时,需要能反应到编译器中。例如新增Square接口,并将它增加到Shape典范中(以下所示),假如未更新caculate()函数,那末就不能编译经过。
  1. interface Square {  kind: "square";  size: number;}type Shape = Rectangle | Circle | Square;
复制代码
  有两种方式能实现这类预警,第一种是在输入编译命令时增加--strictNullChecks参数,并为caculate()函数指定返回值典范,以下所示。
  1. function caculate(s: Shape): number {  switch (s.kind) {    case "rectangle":      return s.height * s.width;    case "circle":      return Math.PI * s.radius ** 2;  }}
复制代码
  由于switch语句没有包含全数典范,是以TypeScript会以为该函数有大要返回undefined,从而就会编译报错。留意,这类方式不太正确,有很多身分(例如函数默许返回数字)会干扰完整性检查,而且--strictNullChecks参数对旧代码有兼容题目。
  第二种方式是操纵never典范,以下代码所示,新增一个能激发典范毛病的assertNever()函数,并在default分支中挪用该函数。
  1. function assertNever(x: never): never {  throw new Error("Unexpected object: " + x);}function caculate(s: Shape) {  switch (s.kind) {    case "rectangle":      return s.height * s.width;    case "circle":      return Math.PI * s.radius ** 2;    default:      return assertNever(s);  }}
复制代码
  固然额外界说了一个函数,可是检查的正确度提拔了很多。
六、索引典范

  索引典范(Index Type)能让编译器检查操纵静态属性的场景,例如从工具中拔取属性的子集,以下所示。
  1. function pluck(obj, names) {  return names.map(n => obj[n]);}
复制代码
  假如要让pluck()函数能从obj工具中乐成的选出names数组所指定的属性,那末需要在声明时设备典范约束,包含names中的元素必须是obj中存在的属性以及返回值典范得是obj属性值的典范,下面经过泛型来描摹这些约束。
  1. function pluck(obj: T, names: K[]): T[K][] {  return names.map(n => obj[n]);}interface Person {  name: string;  age: number;}let person: Person = {  name: "strick",  age: 28};let attrs: string[] = pluck(person, ["name"]);
复制代码
  泛型函数pluck()引入了两个新的典范操纵符,别离是索引典范查询操纵符(keyof T)和索引拜候操纵符(T[K])。前者会取T典范中由公共(public)属性名所组成的团结典范,例如“"name" | "age"”;后者会取T典范中指定属性值的典范,这意味着示例中的person["name"]和Person["name"]两者的典范都是string。
1)字符串索引签名
  keyof T与T[K]一样适用于字符串索引签名,以下面的泛型接口People为例,kType的典范是string和number的团结典范,由于JavaScript里的数值索引会自动转换成字符串索引;vType的典范是number,也就是索引签名的典范。
  1. interface People {  [key: string]: T;}let kType: keyof People;         //string | numberlet vType: People["name"];       //number
复制代码

七、映照典范

  映照典范(Mapped Type)与索引典范类似,也是从现有典范中建立出一种新典范。接下来用一个例子来演示映照典范用法,假定有一个Person接口,它有两个成员,以下所示。
  1. interface Person {  name: string;  age: number;}
复制代码
  当需要将Person接口的每个成员都变成可选或只读的,粗糙的打点方式是一个个的点窜,以下所示。
  1. interface PersonPartial {  name?: string;  age?: number;}interface PersonReadonly {  readonly name: string;  readonly age: number;}
复制代码
  而假如采取映照典范,那末就能快速的改变接口成员,以下代码所示,其中Readonly可将T典范的成员设为只读,而Partial会将它们设为可选。
  1. type Readonly = {  readonly [P in keyof T]: T[P];}type Partial = {  [P in keyof T]?: T[P];}type PersonPartial = Partial;type ReadonlyPerson = Readonly;
复制代码
  [P in keyof T]的语法与索引典范的类似,但内部操纵了for-in遍历语句,其中:
  (1)P是典范变量,会依次绑定到每个成员上,对应成员名的典范。
  (2)T是由字符串字面量组成的团结典范,表示一组成员名,例如“"name" | "age"”。
  (3)T[P]是成员值的典范。
  留意,映照典范描摹的是典范而非成员,假如要增加额外的成员,需要操纵交织典范的方式,以下所示,间接在典范中增加成员会没法经过编译。
  1. //交织典范type ReadonlyNew = {  readonly [P in keyof T]: T[P];} & { data: boolean };//编译毛病type ReadonlyNew = {  readonly [P in keyof T]: T[P];  data: boolean;};
复制代码
  Readonly和Partial是一种同态转换,即在映照时保存源典范的成员名以及其值典范,而且与目标典范相比只要修饰符有差别。而那些会建立新成员、改酿成员典范或其值典范的转换都被称为非同态。由于Readonly和Partial很适用,是以它们已经被包含进TypeScript的标准库里,作为内置的工具典范存在。
八、条件典范

  条件典范(Conditional Type)可以也许表示非同一的典范映照,常以条件表达式举行典范检测,语法类似于三目运算符,从两个典范当选出一个,以下所示。
  1. T extends U ? X : Y
复制代码
  假如T是U的子典范,那末典范将被分解成X,否则是Y。当条件的真假没法确按时,获得的结果将是由X和Y组成的团结典范,以下面的全局函数sum()为例,T是布尔值的子典范,当传入的参数是true时,获得的将是string典范;而传入false时,获得的是number典范。
  1. declare function sum(x: T): T extends true ? string : number;let x = sum(true);        //string | number
复制代码
  假如T或U含包含典范变量,那末就得耽误分解,即等到典范变量都有具体典范后才华盘算出条件典范的结果。鄙人面的示例中,建立了一个Person接口,声明的全局函数add()的返回值典范会按照能否是Person的子典范而改变,而且在泛型函数func()中挪用了add()函数。
  1. interface Person {  name: string;  age: number;  getName(): string;}declare function add(x: T): T extends Person ? string : number;function func<U>(x: U) {  let a = add(x);  let b: string | number = a;}
复制代码
  固然a变量的典范尚不肯定,可是条件典范的结果不是string就是number,是以可以乐成的赋给b变量。
1)散布式条件典范
  当条件典范中被检查的典范是无典范参数(naked type parameter)时,它会被称为散布式条件典范(Distributive Conditional Type)。其出格之处在于它能自动散布团结典范,举个简单的例子,假定T的典范是A | B | C,那末它会被分解成三个条件分支,以下所示。
  1. (A | B | C) extends U ? X : Y//同即是(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
复制代码
  散布式条件典范可以用来过滤团结典范,以下所示,Filter典范可从T中移除U的子典范。
  1. type Filter = T extends U ? never : T;type T1 = Filter;        // "b" | "d"type T2 = Filter void), Function>;      // string | number
复制代码
  散布式条件典范也可与映照典范配合操纵,举行针对性的典范映照,即差别源典范对应差别映照法则,例如映照接口的方式名,以下所示。
  1. type FunctionPropertyNames = {        [K in keyof T]: T[K] extends Function ? K : never    }[keyof T];type T3 = FunctionPropertyNames;      // "getName"
复制代码
  留意,条件典范与团结典范和交织典范类似,不答应递归地援用本身,下面这样写会在编译阶段报错。
  1. type Custom = T extends any[] ? Custom : T;
复制代码
2)典范揣度
  在条件典范的extends子句中,答应经过infer声明引入一个待揣度的典范变量,而且可出现多个同典范的infer声明,例如用infer声明来提取函数的返回值典范,以下所示。有一点要留意,只能在true分支中操纵infer声明的典范变量。
  1. type Func = T extends (...args: any[]) => infer R ? R : any;
复制代码
  当函数具有重载时,就取末端一个函数签名举行揣度,以下所示,其中ReturnType是内置的条件典范,可获得函数典范T的返回值典范。
  1. declare function load(x: string): number;declare function load(x: number): string;declare function load(x: string | number): string | number;type T4 = ReturnType;          // string | number
复制代码
  留意,没法在一般典范参数的约束子语句中操纵infer声明,以下所示。
  1. type Func infer R> = R;
复制代码
  可是可以将约束里的典范变量移除,并将其转移到条件典范中,就能到达类似的结果,以下所示。
  1. type AnyFunction = (...args: any[]) => any;type Func = T extends (...args: any[]) => infer R ? R : any;
复制代码
3)预界说的条件典范
  除了之前示例中用到的ReturnType之外,TypeScript还预界说了4个此外功用的条件典范,以下所列。
  (1)Exclude:从T中移撤除U的子典范。
  (2)Extract:从T中挑选出U的子典范。
  (3)NonNullable:从T中移除null与undefined。
  (4)InstanceType:获得机关函数的实例典范。
  1. type T11 = Exclude;    // "b" | "d"type T12 = Extract;    // "a" | "c"type T13 = NonNullable;     // string | numbertype T14 = ReturnType void>;              // voidclass Programmer {  name: string;}type T15 = InstanceType;              //Programmer
复制代码


免责声明:假如加害了您的权益,请联系站长,我们会实时删除侵权内容,感谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|理想论坛_专业20年的财经股票炒股论坛交流社区 - 股票论坛

GMT+8, 2020-7-5 08:09 , Processed in 0.177651 second(s), 28 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表