Typescript 实战 --- (7)类型兼容性

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Typescript 实战 --- (7)类型兼容性

rogerwu   2020-01-19 我要评论
ts 允许类型兼容的变量相互赋值,这个特性增加了语言的灵活性
 
当一个 类型Y 可以被赋值给另一个 类型X 时,就可以说类型X兼容类型Y。其中,X被称为“目标类型”,Y被称为“源类型”
X兼容Y : X(目标类型) = Y(源类型)

 

1、结构之间兼容:成员少的兼容成员多的
 
基本规则是,如果 X 要兼容 Y,那么 Y 至少具有与 X 相同的属性
interface Named {
  name: string;
}

let x: Named;
let y = { name: 'Chirs', age: 23 };

x = y;
console.log('x', x);   // x { name: 'Chirs', age: 23 }

// 这里要检查 y 是否可以赋值给 x,编译器检查 x 中的每个属性,看能否在 y 中也找到对应的属性

// 相反,把 y 赋值给 x 就会报错,因为 x 不具备 age 属性

y = x;  // Property 'age' is missing in type 'Named' but required in type '{ name: string; age: number; }'

 

1-1、子类型赋值
let s: string = 'hello';

s = null; // 由于在 ts 中, null 是所有类型的子类型,也就是说 字符类型兼容null类型,所以可以赋值

 

1-2、接口兼容性
interface X {
  a: any;
  b: any;
}

interface Y {
  a: any;
  b: any;
  c: any;
}

let x: X = { a: 1, b: '2' }
let y: Y = { a: 3, b: 4, c: 5 }

// 只要源类型y 具备了 目标类型x 的所有属性,就可以认为 x 兼容 y
x = y;
console.log('x', x);   // x { a: 3, b: 4, c: 5 }

 

2、函数之间兼容:参数多的兼容参数少的
 
需要判断函数之间是否兼容,常见于两个函数相互赋值的情况下,也就是函数作为参数的情况
 
2-1、如果要目标函数兼容源函数,需要同时满足三个条件:
 
(1)、参数个数:目标函数的个数 多余 源函数的个数
interface Handler {
  (x: number, y: number): void
}

function foo(handler: Handler) {  // handler:目标函数
  return handler
}

let h1 = (a: number) => {}  // h1:源函数
// 目标函数的参数个数2个 > 源函数参数个数1个
foo(h1);

let h2 = (a: number, b: number, c: number) => {}  // h1:源函数
// 目标函数的参数个数2个 < 源函数参数个数3个
foo(h2);  // 类型“(a: number, b: number, c: number) => void”的参数不能赋给类型“Handler”的参数

 

(2)、参数类型:参数类型必须要匹配
interface Handler {
  (x: number, y: number): void
}

function foo(handler: Handler) {  // handler:目标函数
  return handler
}

let h3 = (a: string) => {}  // h3:源函数

// 尽管目标函数的参数个数多余源函数的参数个数,但是参数类型不同
foo(h3); 

/*
  报错信息:
  类型“(a: string) => void”的参数不能赋给类型“Handler”的参数
  参数“a”和“x” 的类型不兼容
  不能将类型“number”分配给类型“string”
*/
interface Point3D {
  x: number;
  y: number;
  z: number;
}

interface Point2D {
  x: number;
  y: number;
}

// 函数 p3d 和 p2d 的参数个数都是1,参数类型都是对象
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}

// 赋值时,依然采用的是目标函数的参数个数必须大于源函数参数个数,且参数类型相同的原则
p3d = p2d;
p2d = p3d; // 想要不报错,需要关闭 tsconfig.json 中的一个配置  strictFunctionTypes

 

函数的参数之间可以相互赋值的情况,称为 “函数参数双向协变”。它允许把一个精确的类型,赋值给一个不那么精确的类型,这样就不需要把一个不精确的类型断言成一个精确的类型了
 
(3)、返回值类型:目标函数的返回值类型必须与源函数的返回值类型相同,或为其子类型
let p = () => ({ name: 'Bob' })
let s = () => ({ name: 'Bob', age: 23 })

// p 作为目标函数,s 作为源函数时,目标函数的返回值是源函数返回值的子类型
p = s;
s = p;  // 不能将类型“() => { name: string; }”分配给类型“() => { name: string; age: number; }”
 
2-2、关于固定参数、可选参数和剩余参数之间的兼容
 
1)、固定参数可以兼容可选参数和剩余参数
2)、可选参数不兼容固定参数和剩余参数
3)、剩余参数可以兼容固定参数和剩余参数
// 固定参数
let a = (x: number, y: number) => {};
// 可选参数
let b = (x?: number, y?: number) => {};
// 剩余参数
let c = (...args: number[]) => {};

// 固定参数 兼容 可选参数和剩余参数
a = b;
a = c;

// 可选参数 不兼容 固定参数和剩余参数 (可将 strictFunctionTypes 设为false 实现兼容)
b = a;
b = c;

// 剩余参数 兼容 固定参数和可选参数
c = a;
c = b;

 

2-3、函数重载
 
对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名,这样确保了目标函数可以在所有源函数可调用的地方地方
// 源函数
function overload(x: number, y: number): number;
function overload(x: string, y: string): string;

// 目标函数
function overload(x: any, y: any): any{ };
// Error1: 目标函数的参数个数 少于 源函数的参数
// 源函数
function overload(x: number, y: number): number;  
// This overload signature is not compatible with its implementation signature
function overload(x: string, y: string): string;

// 目标函数
function overload(x: any, y: any, z: any): any{ };


// Error2: 目标函数和源函数的返回值类型不兼容
// 源函数
function overload(x: number, y: number): number;  
// This overload signature is not compatible with its implementation signature
function overload(x: string, y: string): string;

// 目标函数
function overload(x: any, y: any) { };

 

3、枚举类型的兼容性
 
(1)、枚举类型和数字类型相互兼容
(2)、枚举类型之间是完全不兼容的
enum Color { Red, Green, Pink };
enum Fruit { Apple, Banana, Orange };

// 枚举类型和数字类型相互兼容

let fruit: Fruit.Apple = 4;
let num: number = Color.Red;

// 相同枚举类型之间不兼容
let c: Color.Green = Color.Red;
// 不能将类型“Color.Red”分配给类型“Color.Green”

// 不同枚举类型之间不兼容

let color: Color.Pink = Fruit.Orange;
// 不能将类型“Fruit.Orange”分配给类型“Color.Pink”

 

4、类兼容性
 
(1)、静态成员和构造函数是不参与比较的,如果两个类具有相同的实例成员,那他们的实例则可以兼容
class A {
  id: number = 1;
  constructor(p: number, q: number) {}
}

class B {
  static s: number = 1;
  id: number = 2;
  constructor(p: number) {}
}

let aa = new A(3, 6);
let bb = new B(8);

// 两个类都含有相同的实例成员 number 类型的id,尽管构造函数不同,依然相互兼容
aa = bb;
bb == aa;

 

(2)、如果两个类中含有相同的私有成员,他们的实例不兼容,但是父类和子类的实例可以相互兼容
class A {
  id: number = 1;
  private name: string = 'hello';
  constructor(p: number, q: number) {}
}

class B {
  static s: number = 1;
  id: number = 2;
  private name: string = 'hello';
  constructor(p: number) {}
}

let aa = new A(3, 6);
let bb = new B(8);

// 在上例的基础上各自添加了相同的 私有成员name,就无法兼容了
aa = bb;
bb == aa;

// 均报错:不能将类型“B”分配给类型“A”,类型具有私有属性“name”的单独声明
class A {
  id: number = 1;
  private name: string = 'hello';
  constructor(p: number, q: number) {}
}

class SubA extends A {}

let aa = new A(3, 6);
let child = new SubA(1, 2)

// 就算包含私有成员属性,但是父类和子类的实例可以相互兼容
aa = child;
child == aa;

 

5、泛型兼容性
 
(1)、如果两个泛型的定义相同,但是没有指定泛型参数,它们之间也是相互兼容的;
// demo 1
interface Empty<T> {};

let a: Empty<string> = {};
let b: Empty<number> = {};

a = b;
b = a;


// demo 2
let log1 = <T>(x: T): T => {
  console.log('x');
  return x
}

let log2 = <U>(y: U): U => {
  console.log('y');
  return y;
}

log1 = log2;

 

(2)、如果泛型中指定了类型参数,会按照结果类型进行比较;
interface NotEmpty<T> {
  value: T;
};

let a: NotEmpty<string> = {
  value: 'string'
};
let b: NotEmpty<number> = {
  value: 123
};

a = b; // 不能将类型“NotEmpty<number>”分配给类型“NotEmpty<string>”

 

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们