typescript中的类型兼容性

函数兼容性

函数参数个数

比如有如下两个函数:

let x = (a: number) => 0
let y = (b: number, c: string) => 0

函数参数个数如果要兼容,需要满足条件:如果对函数 y 进行赋值,则 x 中的每个参数都应在 y 中有对应,也就是 x 的参数个数小于 y 的参数个数。所以有如下结果:

y = x // 没问题
x = y // error,不能将类型“(b: number, c: string) => number”分配给类型“(a: number) => number”。

所以在函数兼容性中,参数个数少的可以赋值给参数个数多的。

 

函数参数类型和返回值类型

除了参数个数,参数的类型也需要对应,有如下三个函数:

let x = (a: number) => 0
let y = (b: string) => 0
let z = (c: string) => false

x 和 y 两个函数的参数个数和返回值都相同,但是参数类型对应不上,所以互相不兼容:

x = y // error,不能将类型“(b: string) => number”分配给类型“(a: number) => number”。参数“b”和“a” 的类型不兼容。

y 和 z 两个函数的参数个数和参数类型都相同,但是返回值类型对应不上,所以互相不兼容:

y = z // error,不能将类型“(c: string) => boolean”分配给类型“(b: string) => number”。不能将类型“boolean”分配给类型“number”。

我们来改造下 y 函数,如下:

let y = (b: string): number | boolean => 0
let z = (c: string) => false

y = z // 没问题

y 函数改造后返回联合类型 number | boolean ,所以可以将 z 赋值给 y。

 

剩余参数和可选参数

当要被赋值的函数参数中包含剩余参数(...args)时,赋值的函数可以用任意个数的参数代替,但类型要对应,如下:

let x = (...args: number[]):number => 0

let y = (a: number, b: number) => a + b

let z = () => 0

x = y // 没问题
x = z // 没问题

x 函数中参数是 ...args,表示可以有 0 个参数,也可以有无限多个参数,所以 y 和 z 函数都可以赋值给 x,前提是参数类型是一样的。

 

剩余参数可以看成无数个可选参数,来看个可选参数和剩余参数结合的例子:

const getNum = (
  arr: number[], // 函数第一个参数是一个各项 number 类型的数组
  callback: (arg1: number, arg2?: number) => number // 第二个参数是函数,它有一个或者两个参数
): number => {
  return callback(...arr) // 应有 1-2 个参数,但获得的数量大于等于 0
}

例子中函数实体里 callback(...arr) 实现了参数中的 callback,callback 参数个数应为 1 个或者 2 个,但是 ...arr 表示参数个数可能是 0 个或者是无限个,当为 0 个时就会不兼容报错。改成下面这样就没问题了:

return callback(arr[0], ...arr)

 

函数参数双向协变 

函数参数双向协变即参数类型无需绝对相同,比如:

let A = function(arg: number|string): void{}
let B = function(arg: number): void{}

A = B // 可以

B = A // 可以

这个例子中,A 和 B 的参数类型并不是完全一样的,A 的参数类型是一个联合类型 number | string,而 B 的参数类型是 number | string 中的 number,这两个函数兼容。但需要注意的是在严格模式下,A = B 不通过,B = A 通过。严格模式的开启是在 tsconfig.json 文件中去掉  "strict": true  的注释。

 

函数重载

带有重载的函数,要求被赋值的函数的每个重载都能在用来赋值的函数上找到对应的签名,例如:
function funcA(arg1: number, arg2: number): number;
function funcA(arg1: string, arg2: string): string;
function funcA(arg1: any, arg2: any): any{
    return arg1 + arg2
}

function funcB(arg1: number, arg2: number): number;
function funcB(arg1: any, arg2: any): any{
    return arg1 + arg2
}

let fn = funcA
fn = funcB // error,不能将类型“(arg1: number, arg2: number) => number”分配给类型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”

例子中 funcB 的重载缺少参数都为 string 类型,返回值为 string 类型的情况,与函数 funcA 不兼容。

 

枚举

数字枚举成员类型与数字类型兼容,比如:

enum Status{
    On,
    Off
}

let s = Status.On

s = 2
s = 3

Status.On 的值为 0,数字枚举成员类型和数值类型互相兼容,所以 s 可以赋值为数字类型。

 

但是不同枚举类型之间是不兼容的:

enum Status{
    On,
    Off
}

enum Color{
    White,
    Black
}

let s = Status.On

s = Color.White  // error,不能将类型“Color.White”分配给类型“Status”

虽然 Status.On 和 Color.White 的值都为 0,但是它们不兼容。

 

字符串枚举成员类型与字符串类型不兼容,比如:

enum Status{
    On = 'on',
    Off = 'off'
}

let s = Status.On

s = 'hi' // error,不能将类型“"hi"”分配给类型“Status”

 

比较两个类类型值的兼容性时,只比较实例的成员,类的静态成员和构造函数不进行比较:

class Animal{
    static age: number // 静态属性 age 是 number 类型
    constructor(public name: string){}
}

class People{
    static age: string // 静态属性 age 是 string 类型
    constructor(public name: string){}
}

class Food{
    constructor(public name: number){} // name 是 number 类型
}

let a: Animal
let p: People
let f: Food

a = p // 通过

a = f // error,不能将类型“Food”分配给类型“Animal”。属性“name”的类型不兼容。

Animal 和 People 的静态属性 age 的类型虽然不相同,但是比较兼容性时不比较静态属性,而它们都有实例属性 name,且都为 string 类型,所以 a = p 可行。Food 的实例属性 name 是 number 类型,所以 a = f 错误。

 

类的私有成员和受保护成员会影响兼容性。比较两个类的兼容性时,当发现有私有成员或者受保护成员时,只有这些成员来自同一个类时才能兼容,即允许子类赋值给父类:

class Parent{
    private age: number
    constructor(){}
}

class Children extends Parent{
    constructor(){
        super()
    }
}

class Other{
    private age: number
    constructor(){}
}

const children: Parent = new Children()

const other: Parent = new Other() // error,不能将类型“Other”分配给类型“Parent”。类型具有私有属性“age”的单独声明。
children 的类型我们指定为了 Parent 类类型,然后给它赋值为 Children 类的实例,没有问题,是因为 Children 类继承 Parent 类,且实例属性没有差异,而 Other 类则不可以。受保护成员同理。
 

泛型

因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如:

interface Data<T>{}

let data1: Data<string>
let data2: Data<number>

data1 = data2 // 通过

虽然两次类型参数 T 的类型不一样,但是因为接口里并没有用到参数 T,所以不管传入什么类型都不影响,两者兼容。相反如果接口中用到参数 T,则结果就相反:

interface Data<T>{
    data: T
}

let data1: Data<string>
let data2: Data<number>

data1 = data2 // error,不能将类型“Data<number>”分配给类型“Data<string>”。不能将类型“number”分配给类型“string”。

 

以上就是类型兼容性的相关知识点。

 

posted @ 2020-05-17 16:34  黑色瓶子  阅读(2542)  评论(0编辑  收藏  举报