# 高级类型
# 交叉类型
交叉类型是将多个类型合并为一个新的类型(并集)。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。交叉类型通过符号&
来实现,例如, Person & Serializable & Loggable
包括 Person
和Serializable
和Loggable
这三种类型。 就是说这个类型的对象同时拥有了这三种类型的成员。示例:
let mergeFunc = <T,U>(arg1:T,arg2:U) => {
let result = {};
result = Object.assign(arg1,arg2); // 合并
return result;
}
上面的代码中,我们合并参数arg1
和arg2
,那么result
应该同时包含T
和U
两种类型。因此,我们如果想要返回确定的类型,我们可以使用交叉类型T&U
。如下所示:
let mergeFunc = <T,U>(arg1:T,arg2:U):T & U => {
let result = {} as T & U;
result = Object.assign(arg1,arg2);
return result;
}
# 联合类型
很多情况下我们希望数据类型可以是多种类型,但是又不希望通过使用any
来丢失对类型的验证,这时候我们可以使用联合类型。联合类型通过符号|
来实现。number|string
表示既可以是number
类型,又可以是string
类型。但是联合类型不同于交叉类型,不可以既包含number
类型的特性又包含string
类型的特性。
let func = (arg:number | string):number => {
let result = 0;
if(typeof arg === 'string'){
result = arg.length;
}else if(typeof arg === 'number'){
result = arg.toString().length;
}
return result;
}
# 类型保护
联合类型适合于那些值可以为不同类型的情况。但当我们想确切地了解是否为某种类型时怎么办?ts
是无法确切地区分开来联合类型数据具体类型。示例:
const arr = ['123',456]
const func2 = ():number | string => {
const nums = Math.random();
if(nums < 0.5){
return arr[0];
}else{
return arr[1]
}
}
let result = func2(); // number | string
if(result.length){ // error:Property 'length' does not exist on type 'number'
console.log(result.length)
}else{
console.log(result.toFixed());
}
上面的代码中,我们定义了一个func2
函数,它会返回number|string
联合类型。但是具体是什么类型无法确定。接下来我们直接把它的某种类型来作为判断result.length
,这样会导致报错,因为ts在编译阶段无法确切地知道result
的具体类型,所以ts
会同时使用number
和string
类型来判断,这样的话就会导致number
类型编译时出现错误。解决方法之一:使用类型断言,确定地告诉ts
类型。
if((result as string).length){
console.log((result as string).length)
}else{
console.log((result as number).toFixed());
}
使用类型断言,在每个使用result
的部分,确切地指定类型可以避免报错。但是这需要在所有地方都使用类型断言。如果使用地方较多,那么会导致不得不多次使用类型断言。不得不多次使用类型断言。因此,我们希望只要检查过一次,在之后都可以直接使用就好。ts
提供了类型保护。
类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词:
function isString(value:number | string):value is string{
return typeof value === 'string';
}
在这个例子里,value is string
就是类型谓词。 类型谓词都是 parameterName is Type
这种形式, parameterName
必须是来自于当前函数签名里的一个参数名,type
必须是指定的联合类型的一种。函数体返回的是一个Boolean
值。
这样的话,每当我们需要使用类型保护时,只需要在一开始调用一下这个函数。TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
if(isString(result)){ // 调用一次即可。
console.log(result.length);
}else{
console.log(result.toFixed());
}
如上所示,我们通过对变量result
调用一次isString
函数,那么result
的变量类型就会缩减为具体的一种类型,在后面所有的使用中都是这种类型。
typeof 类型保护
在上面的代码中,我们使用类型保护去判断变量是不是string
类型,就需要去定义一个函数。如果我们还需要判断number
类型,那么我们还得定义一个简单的类型保护。事实上,对于这种简单地只需要通过typeof xx === string
这种判断,ts可以直接使用typeof
来作为类型保护。示例:
if(typeof result === 'string'){
console.log(result.length);
}else if(typeof result === 'number'){
console.log(result.toFixed());
}
注意:typeof类型保护
- 只有两种形式能被识别:
typeof v === typename
和typeof v !== typename
, typename
必须是number
,string
,boolean
或symbol
。
instanceof 类型保护 instanceof
操作符是用来判断是否是某个构造函数或者类创建的实例。同样ts
提供了instanceof
类型保护,是通过构造函数来细化类型的一种方式。
class Class1{};
class Class2{};
const func2 = ():Class1 | Class2 => {
const nums = Math.random();
if(nums < 0.5){
return new Class1();
}else{
return new Class2()
}
}
let result = func2();
if(result instanceof Class1){ // instanceof 提供类型保护。自动
console.log('result是class1的实例')
}else{
console.log('result是class2的实例')
}
上面的代码中,instanceof
提供了类型保护,自动将实例类型缩小为具体的实例类型。
# 类型别名
类型别名会给一个类型起个新名字,但是它不会新建一个类型,它只是创建了一个新名字来引用那个类型。
类型别名通过type
关键字来定义。示例:
type str = string;
type num = number;
const func = (name:str,age:num):str|num => {
return 111;
}
通过使用type,我们可以给类型起一个名字,在使用该类型时,直接使用名字代替,类型别名的使用有点类似于接口。但是给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
泛型类型别名
同接口一样,类型别名可以使用泛型,我们可以添加类型参数并且在别名声明的右侧传入。
type Name<T> = {name:T}
let n:Name<string> = {name:'hello'};
# 字面量类型
字符串字面量类型
字符串字面量类型允许你指定字符串必须的固定值。也就是这种类型必须是该字符串。示例:
type Name = "hahaha";
let n1:Name = "hahaha"; // 正常赋值
let n2:Name = 'hello'; // error Type '"hello"' is not assignable to type '"hahaha"'.
使用type
指定字面量类型为hahaha
,那么当属于这种类型时,只能为hahaha
,不能是其他值。
在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。示例:
type Direction = "left" | "right";
const getDirection = (direction:Direction):string => {
let result = ""
if(direction === 'left'){
result = 'Go to Left';
}else if(direction === 'right'){
result = 'Go to right'
}else{
result = 'no'
}
return result;
}
如果是联合类型的字符串字面量类型,那么你只能从指定的这几个值中选取。
数字字面量类型
数字字面量类型同字符串字面量类型一致,允许你指定数字必须的固定值。
type Num = 18;
let num1:Num = 18;
let num2:Num = 19; // Type '19' is not assignable to type '18'.
# 枚举成员类型
当枚举的成员满足了一定条件以后,枚举成员可以作为数据类型使用。
- 所有的枚举成员都是未赋值的
- 所有的枚举成员都是数值
- 所有的枚举成员都是字符串
enum A {a};
enum B {b = 1,c = 2};
enum C {d = 'hello',e = 'world}
当枚举的成员,满足上面三种任一类型时,就可以作为一种类型使用,示例:
enum Animal {
Dog = 1,
Cat = 2
};
interface AInterface {
type1:Animal.Dog, // 接口属性定义成枚举成员类型
type2:Animal.Cat, // 接口属性定义成枚举成员类型
};
let animal:AInterface = {
type1:Animal.Dog, // 这里可以使用Animal.Dog
type2:2 // 也可以直接使用对应的数值
}