![2025a6114138014bcec28b0a8d7f5151.png](https://i-blog.csdnimg.cn/blog_migrate/1d09c92375bcf54e8fafadc3f94e7675.jpeg)
- 交叉类型
- 联合类型
- 类型保护
- null和undefined
- 类型保护和类型断言
- 类型别名
- 字面量类型
- 枚举成员类型
- 可辨识类型
交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如,Person & Serializable & Loggable
同时是Person
和Serializable
和Loggable
。 就是说这个类型的对象同时拥有了这三种类型的成员。
const mergeFunc = <T, U>(arg1: T, arg2: U): T & U => {
let res = {} as T & U
//这里使用了类型断言
res = Object.assign(arg1, arg2)
return res;
}
mergeFunc({ a: 'a' }, { b: 'b' }).a
Object.assign是将两个对象属性合并
联合类型
联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况,一个代码库希望传入number
或string
类型的参数。 例如下面的函数:
const getLengthFunc = (content: string | number): number => {
if(typeof content === 'string'){ return content.length }
else return content.toString().length
}
console.log(getLengthFunc('cyang'));
// 输出 5
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird { }
let pet = getSmallPet();
pet.layEggs();
pet.swim(); // Error
这里的联合类型可能有点复杂,但是你很容易就习惯了。 如果一个值的类型是A | B
,我们能够确定的是它包含了A
和B
中共有的成员。 这个例子里,Bird
具有一个fly
成员。 我们不能确定一个Bird | Fish
类型的变量是否有fly
方法。 如果变量在运行时是Fish
类型,那么调用pet.fly()
就出错了。
类型保护
typeof
TypeScript可以将typeof
识别为一个类型保护,也就是说我们可以直接在代码里检查类型。
function getName(value: string, padding: string | number) {
if (typeof padding === "number") {
return padding.toString() + value
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
console.log(getName('cyang', 1));
// 结果 1cyang
console.log(getName('cyang', '1'));
// 结果 1cyang
这些*typeof
类型保护*只有两种形式能被识别:typeof v === "typename"
和typeof v !== "typename"
,"typename"必须是:"number" "string" "boolean"
或"symbol"
。 但是TypeScript并不会阻止你与其它字符串比较,只是语言不会把那些表达式识别为类型保护。
instanceof
instanceof
类型保护是通过构造函数来细化类型的一种方式。
class CreatedByClass1 {
public age: number = 18
constructor() { }
}
class CreatedByClass2 {
public name: string = "cyang"
constructor() { }
}
function getRandomItem() {
return Math.random() < 0.5 ? new CreatedByClass1() : new CreatedByClass2()
}
const item1 = getRandomItem()
if (item1 instanceof CreatedByClass1) {
console.log(item1.age);
// 结果 18
} else {
console.log(item1.name);
// 结果 cyang
}
instanceof
的右侧要求是一个构造函数,左侧的类型细化为:
- 该类实例的类型(构造函数
prototype
属性的类型) - (构造函数存在重载版本时)由构造函数返回类型构成的联合类型
Null和Undefined
默认情况下,类型检查器认为null
与undefined
可以赋值给任何类型。null
与undefined
是所有其它类型的一个有效值。
let str1 = 'cayng'
str1 = undefined // Error不能将类型“undefined”分配给类型“string”。
let str2: string | undefined = 'cyang'
str2 = undefined
str2 = null // Error不能将类型“null”分配给类型“string | undefined”。
注意,按照JavaScript的语义,TypeScript会把null
和undefined
区别对待。string | null
,string | undefined
和string | undefined | null
是不同的类型。
可选参数和可选属性
使用了--strictNullChecks
,可选参数会被自动地加上| undefined
:
const sumFunc = (x: number, y?: number) => {
return x + (y || 0)
}
sumFunc(1, undefined)
这里的可选参数y就被赋予了类型undefined
。
同样,可选属性也会有同样的处理:
class OptClass {
a: number
b?: number
}
let opt = new OptClass()
opt.b = undefined
类型保护和类型断言
由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除null
。
const getLengthFunc = (value: string | null): number => {
if(value === null) return 0
else return value.length
}
这里去除了null
,你也可以使用短路运算符:
const getLengthFunc = (value: string | null): number => {
return (value || '').length
}
当然如果编译器不能够去除null
或undefined
,你可以使用类型断言手动去除。 语法是添加!
后缀 例如:identifier!
从identifier
的类型里去除了null
和undefined
:
function getSpliced(num: number | null): string {
function getRes(prefix: string) {
return prefix + num!.toFixed().toString()
}
num = num || 0.1
return getRes('取整后的值为')
}
console.log(getSpliced(2.4));
// 结果: 取整后的值为2
类型别名
类型别名type
会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
简单定义一个,
type TypeString = string
let str1: TypeString = 'cyang'
泛型的类型别名
type PositionType<T> = { x: T, y: T }
const postion1: PositionType<number> = {
x: 1,
y: -1,
}
const postion2: PositionType<string> = {
x: 'cyang',
y: 'yangc',
}
我们也可以使用类型别名来在属性里引用自己:
type Child<T> = {
current: T,
child?: Child<T>,
}
let child: Child<string> = {
current: 'cyang',
child: {
current: 'yangc',
// ...
}
}
与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。
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 typename = typename[] //Error 类型别名“typename”循环引用自身。
类型别名看起来可以像接口一样,
type Alias = {
num: number
}
interface Interface {
num: number
}
let _alias: Alias = {
num: 1
}
let _interface: Interface = {
num: 1
}
_alias = _interface
但是却有着本质的区别。类型别名不能被extends
和implements
自己也不能extends
和implements
其它类型)。尽量去使用接口代替类型别名。
另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
字面量类型
指定类型为一个具体的值。
字符串字面量类型
在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。
字符串字面量类型允许你指定字符串必须的固定值,
type Name = 'cyang'
const name1: Name = 'cyang'
const name2: Name = 'str'// Error不能将类型“"str"”分配给类型“"cyang"”。
你可以实现类似枚举类型的字符串。
type Direction = 'north' | 'east' | 'south' | 'west'
function getDirectionFirstLetter(direction: Direction) {
return direction.substr(0, 1)
}
console.log(getDirectionFirstLetter('east'));
getDirectionFirstLetter('str') //Error类型“"str"”的参数不能赋给类型“Direction”的参数。
字符串字面量类型还可以用于区分函数重载:
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
数字字面量类型
type Age = 18
interface InfoInterface {
name: string,
age: Age,
}
const _info: InfoInterface = {
name: 'cyang',
age: 18,
}
枚举成员类型
当所有枚举成员都拥有字面量枚举值时,枚举成员就变成了一种类型。
enum Animals {
Dog = 1,
Cat = 'string',
}
interface Dog {
type: Animals.Cat
}
const dog: Dog = {
type: Animals.Cat
}
console.log(dog)
//结果 {type: "a"}
可辨识联合类型
你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式,它也称做标签联合或代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:
- 具有普通的单例类型属性— 可辨识的特征。
- 一个类型别名包含了那些类型的联合— 联合。
- 此属性上的类型保护。
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
首先我们声明了将要联合的接口。 每个接口都有kind
属性但有不同的字符串字面量类型。kind
属性称做可辨识的特征或标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起:
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;
}
}
完整性检查
在使用可辨识联合类型时防止出错。
当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们少写了一种类型判断:
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;
//...
}
}
这里少写了一种可辨识联合类型。
有两种方式可以实现。 首先是启用 --strictNullChecks
并且指定一个返回值类型:
function area(s: Shape): number { // Error: 类型“Circle”的参数不能赋给类型“never”的参数。t
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
//...
}
}
最新版好像是默认启动的,如果没有报错可以再配置文件中设置。
因为switch
没有包涵所有情况,所以TypeScript认为这个函数有时候会返回undefined
。 如果你明确地指定了返回值类型为number
,那么你会看到一个错误,因为实际上返回值的类型为number | undefined
。 然而,这种方法存在些微妙之处且--strictNullChecks
对旧代码支持不好。
第二种方法使用never
类型,编译器用它来进行完整性检查:
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
//...
default: return assertNever(s); // Error 类型“Circle”的参数不能赋给类型“never”的参数。
}
}
这里,assertNever
检查s
是否为never
类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么s
将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数,但是在你忘记某个case的时候也更加明显。
这章节内容比较多,可以记下来慢慢理解。下一章节学习高级类型2。