在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
交叉类型交叉类型,就是将多个类型合并为一个新的类型,这个新的类型具有这多个类型的成员,含有这几个类型的所有特性,是他们的综合体,像是集合的并集 例子: function extend<T,U>(first: T, second: U): T&U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = first[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = second[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class myLoggable implements Loggable { log() { console.log('qwe'); } } let jim = extend(new Person('qq'), new myLoggable()); console.log(jim.name); jim.log(); 例子中jim有Person中的name属性也有myLoggable中的log()方法
联合类型联合类型,不像是交叉类型是多个类型的合集,表示是这多个类型中的一种类型,像是集合中的交集,只有多个类型中共有的特性才可以被调用 例如要向一个函数传递参数,这个参数可能是number也可能是string function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4); // " Hello world" 这里存在一个问题,将padding定义为any,表示我们可以传递任何值给padding,这个错误TypeScript是不会报错的,只有在编译时才会报错
解决这个问题,可以采用联合类型,用竖线分割每个类型,表示是这几个类型中的一种 function padLeft(value: string, padding: string | number) { ........ } let f = padLeft("Hello world", true); // error
如果一个值是联合类型,就只能访问这多个类型所共有的成员 interface Bird { fly(); layEggs(); } interface Fish { swim(); layEggs(); } function getSmallPet(): Fish | Bird { ... } let pet = getSmallPet(); pet.layEggs(); // okay pet.swim(); // errors getSmallPet的返回类型就是一个联合类型,所以pet就只能访问Bird和Fish的共有成员layEggs() 在上面的例子中,我们不知道pet到底是那种类型,所以就不可能去访问哪些不是公共的成员,如果我们知道了pet的类型,就可以访问该类型的所有成员了
类型保护和区分类型为了解决上面提到的具体类型的确定问题,需要引入类型断言(类型转换) let pet = getSmallPet(); if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); } 问题显而易见,每次都需要对pet进行类型转换,麻烦
用户自定义的类型保护类型保护就可以解决上述每次都要进行类型断言的缺点,类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型断言 function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined; }
pet is Fish就是类型断言。 一个断言是 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。 // 'swim' 和 'fly' 调用都没有问题了 if (isFish(pet)) { pet.swim(); } else { pet.fly(); } TypeScript不仅知道在if里是Fish,而且还知道在else里是Bird类型
typeof类型保护我们将之前的padLeft代码改用类型断言来实现 function isNumber(x: any): x is number { return typeof x === "number"; } function isString(x: any): x is string { return typeof x === "string"; } function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value; } if (isString(padding)) { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
如果要按这样写就要为每个原始类型写一个函数,麻烦。TypeScript会把"typeof v === typeofname"和"typeof v !== typeofname"看做是类型保护,所以就不必为一个原始类型写一个函数,直接用typeof就可以了 function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
instanceof类型保护instanceof类型保护是通过构造函数来细化类型的一种方式 interface Padder { getPaddingString(): string } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) { } getPaddingString() { return Array(this.numSpaces + 1).join(" "); } } class StringPadder implements Padder { constructor(private value: string) { } getPaddingString() { return this.value; } } function getRandomPadder() { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(" "); } // 类型为SpaceRepeatingPadder | StringPadder let padder: Padder = getRandomPadder(); if (padder instanceof SpaceRepeatingPadder) { padder; // 类型细化为'SpaceRepeatingPadder' } if (padder instanceof StringPadder) { padder; // 类型细化为'StringPadder' }
类型别名类型别名就是给类型起一个别名,而且可以用于基础的数据类型 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 Alias = { num: number } interface Interface { num: number; } declare function aliased(arg: Alias): Alias; declare function interfaced(arg: Interface): Interface; 在上面的代码中interfaced返回值的类型是Interface,而aliased返回值的类型是对象字面量 类型别名与接口还有一个地方不同的是类型别名不可以被extends和implements 上面说了类型别名与接口的两个不同点,当然也有相同点,即都可以使用泛型 type Tree<T> = { value: T; left: Tree<T>; right: Tree<T>; }
字符串字面量类型字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合 type Easing = "ease-in" | "ease-out" | "ease-in-out"; class UIElement { animate(dx: number, dy: number, easing: Easing) { if (easing === "ease-in") { // ... } else if (easing === "ease-out") { } else if (easing === "ease-in-out") { } else { // error! should not pass null or undefined. } } } let button = new UIElement(); button.animate(0, 0, "ease-in"); button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here 只能从规定的三种类型中选择一种类型进行传递,其他的类型会报错
可辨识联合可以合并字符串字面量类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式 先定义三个将要联合的接口,每个接口都有kind属性,但是值不同,king属性可以作为可辨识的特征和标识 interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; }
然后将他们联合到一起 type Shape = Square | Rectangle | Circle;
使用可辨识联合 function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } }
注意:如果在Shape中添加新的类型,那么在switch下也要添加相应的判断
参考资料: TypeScript中文网 · TypeScript——JavaScript的超集 |
请发表评论