TypeScript 高级类型


作者:Seiya

时间:2019年08月01日


交叉类型


交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

function extend<First, Second>(first: First, second: Second): First & Second {
	const result: Partial<First & Second> = {};
	for (const prop in first) {
		if (first.hasOwnProperty(prop)) {
			(<First>result)[prop] = first[prop];
		}
	}
	for (const prop in second) {
		if (second.hasOwnProperty(prop)) {
			(<Second>result)[prop] = second[prop];
		}
	}
	return <First & Second>result;
}

tips

交叉类型和联合类型之间的区别:

  • 交叉类型表示包含所需的所有类型的特性之和;

  • 联合类型表示所需的类型之一;




类型别名


类型别名用来给一个类型起个新名字。 我们使用 type 创建类型别名:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
	if (typeof n === 'string') {
		return n;
	} else {
		return n();
	}
}

tips

类型别名常用于联合类型。




字符串字面量类型


字符串字面量类型用来约束取值只能是某几个字符串中的一个。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
	// do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick');	// error

上例中,我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。


注意:

类型别名与字符串字面量类型都是使用 type 进行定义。




类型保护


JavaScript 里常用来区分2个可能值的方法是检查成员是否存在:

let pet = getSmallPet();

// 每一个成员访问都会报错
if (pet.swim) {
	pet.swim();
}
else if (pet.fly) {
	pet.fly();
}

为了让这段代码工作,我们要使用类型断言:

let pet = getSmallPet();

if ((<Fish>pet).swim) {
	(<Fish>pet).swim();
}
else {
	(<Bird>pet).fly();
}


自定义的类型守卫


类型守卫就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型守卫,我们只要简单地定义一个函数,它的返回值是一个类型谓词:

function isFish(pet: Fish | Bird): pet is Fish {
	return (<Fish>pet).swim !== undefined;
}


typeof 类型守卫


我们可以像下面这样利用类型断言来写:

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}'.`);
}

然而,必须要定义一个函数来判断类型是否是原始类型,这太痛苦了。 幸运的是,现在我们不必将typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型守卫:

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'
}



可以为 null 的类型


TypeScript 具有两种特殊的类型,null 和 undefined,它们分别具有值 null 和 undefined。默认情况下,类型检查器认为null与undefined可以赋值给任何类型。 null与undefined是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。


--strictNullChecks标记可以解决此错误:

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是不同的类型。

最后更新时间: 2019-8-2 16:31:09