必须同时满足这俩接口里面的成员属性
interface DogInterface{
run():void;
}
interface CatInterface{
jump():void;
}
const pet: DogInterface & CatInterface={
run(){},
jump(){}
}
声明的类型并不确定,可以为多个类型中的一个
let age:string|number = '10'; // 可以为字符串
let age1:string|number = 10; // 也可以为数字
自变量类型
有的时候我们不仅需要限定一个变量的类型,而且还要限定变量的取值,在某一个特定的范围内
// 自变量的联合类型
let age2: 10|20|30 = 10;
对象的联合类型
在类型不确定的情况下,只能访问所有类型的公有成员,即:取所有类型成员的交集
interface DogInterface{ run():void; }
interface CatInterface{ jump():void; }
class Dogg implements DogInterface{
run(){}
eat(){}
}
class Catt implements CatInterface{
jump(){}
eat(){}
}
enum Types{Dog,Cat}
function getAnimal(type:Types){
// 此处animal的类型为 animal: Dogg | Catt
const animal = Types.Dog === type?new Dogg():new Catt();
animal.eat(); //此时只能访问eat方法,因为eat是Dogg | Catt都有的方法.
// animal.run(); //Error 类型“Dogg | Catt”上不存在属性“run”。
return animal;
}
可区分的联合类型:结合了联合类型和自变量类型的一种类型保护方法
一个类型如果是多个类型的联合类型,并且 每个类型之间有一个公共的属性,那么我们就可以凭借这个公共属性,创建不同得类型保护区块,看下面的例子
interface SquareInterface{
types:'Square';
width:number
}
interface CircularInterface{
types:'circular';
radius:number
}
type Shape = SquareInterface | CircularInterface; // 联合类型
function getArea(s:Shape){
switch(s.types){ // 通过两个类型的通用属性type,来创建不同的类型保护区块
case 'Square':
return s.width * s.width; // 在这可以访问SquareInterface
case 'circular':
return s.radius * s.radius * 3.14; // 在这可以访问CircularInterface
}
}
let ss: SquareInterface = {
types: 'Square',
width:100
}
getArea(ss); // 10000
上面的案例如果添加一种新的保护区块会怎么样呢
// 添加一个长方形
interface RectangleInterface{
types:'rectangle';
width: number;
height:number
}
// 修改联合类型
type Shape = SquareInterface | CircularInterface | RectangleInterface;
// 使用
getArea( {
types: 'rectangle',
width:100, height:100
});
//结果: undefined
很显然上面的getArea(...)的结果是undefined,那么要怎么得到错误提示呢,有两种方法
1.给方法确定返回值,如果getArea()返回undefined,则不能满足;
function getArea(s:Shape):number{
...
}
此时给Shape新增联合类型的时候,由于getArea没有对应的方法,所以number则会提示报错
2.添加never
function getArea(s:Shape){
switch(s.types){
...
default:
// 检查s是不是never类型,如果s是never类型,则前面所有的分支全部被覆盖,这个分支就不会走到, 如果是不是never类型,则前面的分支就遗漏掉
return ((e: never) => { throw new Error(e) })(s)
}
}
此时再给getArea传入不满足要求的参数,则会抛出一个错误信息
先看一个例子
const obj = { a:1,b:2,c:3 }
function getArr(obj:any,keys:string[]){
return keys.map(key=>obj[key])
}
console.log(getArr(obj,['b','c'])); // [2,3]
console.log(getArr(obj,['r','s'])); // [undefined,undefined]
当keys里面传了obj里面并不存在的数据是得到undefined,此时并没有报错.那么ts要怎么对这种类型进行约束呢?此时就要用到索引类型
索引类型的操作符
keyof T 表示 类型T的所有公共属性的自变量的联合类型
T[K] 类型T的属性K代表的类型
T extends U 泛型变量可以通过继承某个类型或者某个属性
接下来我们利用索引类型来改造上面的代码,就不会出现
const obj = { a:1,b:2,c:3 }
function getArr(obj:T,keys:K[]):T[K][]{
return keys.map(key=>obj[key])
}
console.log(getArr(obj,['r','s']));
// 此时编辑器会报错。Type 'string' is not assignable to type '"a" | "b" | "c"'.
可以从一个旧的类型生成一个新的类型,例如,我们有如下一个接口,是可以随意修改的。
interface Obj { a:number;b:string}
let obj1: Obj = {a:1,b:'d'}
obj1.a = 3; // OK
然后,我们看下,怎么把上面这个接口变成只读或者可选呢的呢,如下代码
// 设置只读:模拟Readonly
type ReadonlyNew = {
readonly [P in keyof T]:T[P] // 索引出T中的所有元素(相当于for循环),设置成readonly,然后原样返回
}
type ReadonlyObj = ReadonlyNew; // 只读
let obj2: ReadonlyObj = {a:2,b:'c'};
obj2.a = 3; // ERROR Cannot assign to 'a' because it is a constant or a read-only property.
//设置为可选: 模拟Partial
type PartialNew = {
readonly [P in keyof T]?:T[P]
}
type PartialObj = PartialNew; // 可选
let obj3: PartialObj = { a: 2 } //OK
let obj4: Obj = { a: 2 } //Error
此时眼尖的同学看出来了,ReadonlyNew/PartialNew就是和TS内置的Readonly/Partial方法。
此外再介绍一种抽取Object子集的方法Pick,模拟Pick
interface Obj1 { a:number;b:number;c:number;d:number;}
type PickNew = { // T中满足K的被保留了下来
[P in K]:T[P] // 从K中选取,然后从T中返回
}
type PcikObj = PickNew; // 此时PcikObj里面就只有a和d两项了
以上三种类型Readonly/Partial/Pick,官方有个称号,叫同态,意味着只能作用于object属性,而不会引入新的属性
那么非同态是什么样的呢,我们来看下Record
interface Obj1 { a: number; b: number; c: number; d: number;}
type RecordObj = Record<'x'|'y',Obj1>
// 此时可以看到RecordObj是这样的
RecordObj = {
x: Obj1;
y: Obj1;
}
T extends U ? X : Y 这一段是什么意思呢?
解:如果类型T可以被赋值给类型U,那么结果就是X类型,否则就是Y类型
type TypeName =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends Function ? "function" :
T extends undefined ? "undefined" :
"object"
let t1: TypeName = 'string';
type T2 = TypeName; // type T2 = "number"
(A|B) extends U ? X : Y 可拆解成 (A extends U ? X : Y ) | (B extends U ? X : Y )
type t3 = TypeName
根据这个特性可以实现一些类型的过滤,举个例子
type Diff = U extends K ? never : U;
type dif = Diff<"a" | "b" | "c", "a" | "e">;
// 结果: type dif = "b" | "c";
// (A|B) extends U ? X : Y 可拆解成 (A extends U ? X : Y ) | (B extends U ? X : Y )
// 根据上面的特性将Diff<"a" | "b" | "c", "a" | "e">拆卸成如下:
type _dif1 = Diff<"a", "a" | "e">; // never
type _dif2 = Diff<"b", "a" | "e">; // b
type _dif3 = Diff<"c", "a" | "e">; // c
type _dif = _dif1 | _dif2 | _dif3; // type _dif = "b" | "c";结果同diff
Diff的扩展,// 过滤掉某些类型
type NotNull = Diff;
type ts = NotNull; // string|number
这些方法官方同样的有内置方法,对应的为
Exclude
-- 从T
中剔除可以赋值给U
的类型。Extract
-- 提取T
中可以赋值给U
的类型。NonNullable
-- 从T
中剔除null
和undefined
。ReturnType
-- 获取函数返回值类型。InstanceType
-- 获取构造函数类型的实例类型。type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T02 = Exclude void), Function>; // string | number
type T03 = Extract void), Function>; // () => void
type T04 = NonNullable; // string | number
type T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]
function f1(s: string) {
return { a: 1, b: s };
}
class C {
x = 0;
y = 0;
}
type T10 = ReturnType<() => string>; // string
type T11 = ReturnType<(s: string) => void>; // void
type T12 = ReturnType<(() => T)>; // {}
type T13 = ReturnType<(() => T)>; // number[]
type T14 = ReturnType; // { a: number, b: string }
type T15 = ReturnType; // any
type T16 = ReturnType; // any
type T17 = ReturnType; // Error
type T18 = ReturnType; // Error
type T20 = InstanceType; // C
type T21 = InstanceType; // any
type T22 = InstanceType; // any
type T23 = InstanceType; // Error
type T24 = InstanceType; // Error