typescript高级类型2
instanceof类型保护
如果你已经阅读了typeof类型保护并且对JavaScript里的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'
}
instanceof的右侧要求是一个构造函数,TypeScript将细化为:
此构造函数的prototype属性的类型,如果它的类型不为any的话
构造签名所返回的类型的联合
以此顺序。
可以为null的类型
TypeScript具有两种特殊的类型,null和undefined,它们分别具有值null和undefined. 我们在基础类型一节里已经做过简要说明。 默认情况下,类型检查器认为null与undefined可以赋值给任何类型。 null与undefined是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。 null的发明者,Tony Hoare,称它为价值亿万美金的错误。
–strictNullChecks标记可以解决此错误:当你声明一个变量时,它不会自动地包含null或undefined。 你可以使用联合类型明确的包含它们:
let s = "foo";
s = null; // 错误, 'null'不能赋值给'string'
let sn: string | null = "bar";
sn = null; // 可以
sn = undefined; // error, 'undefined'不能赋值给'string | null'
注意,按照JavaScript的语义,TypeScript会把null和undefined区别对待。 string | null,string | undefined和string | undefined | null是不同的类型。
可选参数和可选属性
使用了–strictNullChecks,可选参数会被自动地加上| undefined:
function f(x: number, y?: number) {
return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
可选属性也会有同样的处理:
**class C {
a: number;
b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'**
类型保护和类型断言
由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除null。 幸运地是这与在JavaScript里写的代码一致:
function f(sn: string | null): string {
if (sn == null) {
return "default";
}
else {
return sn;
}
}
这里很明显地去除了null,你也可以使用短路运算符:
function f(sn: string | null): string {
return sn || "default";
}
如果编译器不能够去除null或undefined,你可以使用类型断言手动去除。 语法是添加!后缀:identifier!从identifier的类型里去除了null和undefined:
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
本例使用了嵌套函数,因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式)。 因为它无法跟踪所有对嵌套函数的调用,尤其是你将内层函数做为外层函数的返回值。 如果无法知道函数在哪里被调用,就无法知道调用时name的类型。
类型别名
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
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 Container<T> = { value: T };
我们也可以使用类型别名来在属性里引用自己:
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
然而,类型别名不能出现在声明右侧的任何地方。
type Yikes = Array<Yikes>; // error
上一篇: C++知识点全面汇总
下一篇: TypeScript 高级类型整理