Typescript 乃 JavaScript 子集。只要设置一下编译器为非严格模式,那么所有 JavaScript 代码皆是合法的 TypeScript 代码。为了可以适应不同的场景 TypeScript 尽可能做到非常灵活。本文中,我们将会深入了解 TypeScript 中的类型兼容性,并尝试解释什么是结构类型系统(Structure Type System)。
TypeScript is a superset of JavaScript. Any JavaScript code is a valid TypeScript code, as long we set the compiler not to be strict. Therefore, TypeScript aims to be as flexible as possible so that it can apply to various situations. In this article, we look into type compatibility in TypeScript and explain what a structural type system is.

相当于的语言属于“标明类型系统(Nominal Type System)”。下面的例子说明,只有两个变量是同一类型的才是兼容的。我们来看看 C# 代码。
There are a lot of languages with a nominal type system. The above means that the two variables are compatible if they are of the same type. Let’s examine this C# code:

public class Employee
{public string name;public Employee(string name){this.name = name;}
}public class Person
{public string name;public Person(string name){this.name = name;}
}Employee john = new Employee('John');
john = new Person('John');

显然以上 C# 代码有错误。在标明类型系统下,Employee 与 Person 并不等价兼容的。类似地,像 Java 或 C++ 那些语言中也是同样的情况。
The above C# code causes an error. Due to a nominal type system, Employee and Person are not compatible. Similarly, such situations occur in languages like Java and C++.

这种好处是能让我们减少类型不匹配的错误,但就是不太灵活。TypeScript 中的结构类型系统则给我们带来更多的自由程度。
The above behavior might help us to prevent mismatching types, but it is not very flexible. To give us more freedom, TypeScript implements a structural type system.

结构类型系统 Structural type system

在结构类型系统的语言中,两种类型是否等价是通过结构来决定的,而不是名称。下面的 TypeScript 写起来,其实很像我们之前写 JavaScript 那样子的写法。
In a language with a structural type system, two types are compatible judging by their structure, instead of the name. The above allows TypeScript to adjust to the way that we often write the JavaScript code.

type Employee = {name: string;
}class Person {public constructor (readonly name: string) {}
}const john: Employee = new Person('John');

Person 类的构造器里面因为有 readonly 关键字,所以就会分配一个 name 的属性。
Above, we simplify the assignment of properties in the constructor of the Person class with the use of the readonly keyword.

TypeScript 中上述代码完全合法。更进一步说,即使不完全相同的类型,只要结构一致我们都可以去使用。
In TypeScript, the above code is perfectly valid. Going even further, we can use types that are not identical when it comes to its structure.

结构化子类型 Structural subtyping

要使得一种类型等价于其他类型,至少要一个相同的属性。
For one type to be compatible with the other, it needs to have at least the same properties.

interface Employee {name: string;workplaceType: string;
}interface Person {name: string;
}function printName(person: Person) {console.log(person.name);
}

由于 Employee 已经有 Person 的所有属性,所以下面给出 printName 函数可以传入 Employee
Above, we have the printName function. Since the Employee has all the properties of a Person.

const john: Employee = {name: 'John',workplaceType: 'Music store'
}printName(john);

但反过来,要让 Employee 等价于 Person,却是不行的。
The fact that the Person is compatible with the Employee does not mean that it works the other way around.

function printWorkplace(person: Employee) {console.log(person.name);
}const john: Person = {name: 'John',
}printWorkplace(john);

传参 “Person” 没分配到 “Employee” 的参数类型。 “Person” 类型中没有 “workplace”属性,而在“Employee” 是必须的。
Argument of type ‘Person’ is not assignable to parameter of type ‘Employee’. Property ‘workplaceType’ is missing in type ‘Person’ but required in type ‘Employee’.

错误的原因就是 Person 没有 Employee 所有的属性。
The above happens because the Person does not have all the properties of the Employee.

当 Employee 继承 Person 时候也是差不多这个意思,这是子类型的情形。
Similar subtyping happens when the Employee extends the Person.

interface Person {name: string;
}interface Employee extends Person {workplaceType: string;
}

在标记式子类型语言中,实现等价兼容的唯一方式就是子类型。好在 TypeScript 是结构化类型语言,不然的话,“accidental” subtypes work issue-free.(抱歉 不会翻译)。
In languages with nominal subtyping, it would be the only way to achieve compatible subtypes. Thanks to TypeScript being a structurally typed language, instead, “accidental” subtypes work issue-free.

多态 Polymorphism

printName(person: Person)函数传参数 Employee,这种就是多态。Employee 拥有 person 所有的属性,故此可以多态,包括其实例。
Calling the printName(person: Person) function using the Employee is an example of polymorphism. Since we know that the employee has all the properties of a person, we can treat its instance as such.

最简单直观的还是型状的例子,计算其面积。
The most straightforward way to visualize it is with the use of an example with shapes and calculating their areas.

interface Shape {getArea(): number;
}class Circle {constructor(readonly radius: number) {}getArea() {return Math.pow(this.radius, 2) * Math.PI;}
}class Square {constructor(readonly size: number) {}getArea() {return Math.pow(this.size, 2);}
}

虽然 Circle 或 Square 都没有继承 Shape,但是都有 getArea 函数。所有只要我们需要,都可以把圆形或者方形当作图形型状。
Although neither Circle nor Square extends Shape explicitly, all of them have the getArea function. If that’s all we need, we can treat Circle and Square as a Shape.

const shapes = [new Circle(10),new Square(5),new Circle(2),new Square(25)
]const sortedShapes = shapes.sort((firstShape: Shape, secondShape: Shape) => (firstShape.getArea() - secondShape.getArea()
))

区分类型守卫 Differentiating types with Type Guards

不过,一般都没那么简单的类型。有时我们面对的是联合或未知类型。好在 TypeScript 已经考虑到这一点,我们做起来没那么辛苦。好比说在获取 API 的异步数据,假设是获取 user 并打印其 workplace(当类型是 Employee 的时候)。
We don’t always have such straightforward types. Sometimes, we need to deal with unions and the unknown. Thankfully, TypeScript has mechanisms to help us with that. Such a situation might occur when we fetch the data from various APIs. Let’s say we want to fetch a user and print his workplace type if he is an employee.

function printWorkplaceType(employee: Employee) {console.log(employee.workplaceType);
}type FetchUser = () => Promise<unknown>;fetchUser().then((user) => {printWorkplaceType(user);})

坏消息是上面的代码跑不通的,抛出下面的错误。
Unfortunately, the above does not work. We experience an error:

传参 “unknown” 没分配到 “Employee” 的参数类型。 “{}” 类型中没有 “name”、“workplace”属性,而在 “Employee” 是必须的。
Argument of type ‘unknown’ is not assignable to parameter of type ‘Employee’. Type ‘{}’ is missing the following properties from type ‘Employee’: name, workplaceType

原因是我们不能保证获取的就有 Employee 属性。乍看之下我们好像可以先检查一下 workplaceType 属性是否存在。
This is because we are not sure if what we fetch is a proper employee. At first glance, we might want to check the existence of the workplaceType property.

fetchUser().then(user => {if (user.workplaceType) {printWorkplaceType(user);}})

上面代码也是不行的。unknow 类型并没有一个workplaceType属性。哪怕我们检查这个属性但是编译器并不认为它是 Employee 的属性。
The above does not work either, because Property ‘workplaceType’ does not exist on type ‘unknown’. Even if we could check this property, the compiler wouldn’t treat it as a proper Employee.

我们也不能使用 instanceof 操作符,这是因为 Employee 是一个接口的缘故。针对该问题,不是没有办法,办法就是“类型守卫”。
We also can’t use the instanceof operator, because the Employee is just an interface. A solution to this issue are Type Guards.

定义类型守卫 Defining Type Guards

类型守卫就是在运行时保证某个类型的函数。
A type guard is a function that guarantees a type during a runtime check.

function isEmployee(user: any): user is Employee {return Boolean(user.workplaceType && user.name);
}

user is Employee 意思是类型预测。类型预测是特殊的返回类型表示返回特定的类型是什么。
The user is Employee is a type predicate. Type predicates are a special return type that signals the type of a particular value.

现在我们就可以在逻辑中轻松实现,保证返回的 user 是 Employee 类型。
Now, we can easily implement it in our logic to make sure that what we fetch is a proper Employee.

fetchUser().then(user => {if (isEmployee(user)) {printWorkplaceType(user);}})

上述代码中只要检查了类型那么我们就可以安全地调用 printWorkplaceType 函数。
In the above code, we check the type in the runtime so that we can safely call the printWorkplaceType function.

我们可以使用 in 操作符,会更清晰一点。
We can make our code a bit cleaner using the in operator.

in 操作符的作用是判断对象或其原型链上是否存在某个指定的属性 The in operator returns true if the
specified property is in the specified object or its prototype chain.

function isEmployee(user: any): user is Employee {return 'workplaceType' in user && 'name' in user;
}

总结 Summary

本文中我们考察了两种类型系统:结构化的和标记式的。我们也了解 TypeScript 使用结构化类型系统的原因和理由,而且明白了,TypeScript 中的多态是什么,以及如何去应用多态,那都不需要明确扩展接口就可以。在某些地方我们用到了类型守卫和类型预测,还有 in 操作符。
In this article, we’ve reviewed two types of systems: structural and nominal. We’ve also looked through the consequences and reasons of TypeScript having the structural type system. We explained what polymorphism is and how we can apply it to TypeScript without extending interfaces explicitly. To help us in some situations, we’ve used type guards with type predicates and the in operator.

参考:

  • https://medium.com/redox-techblog/structural-typing-in-typescript-4b89f21d6004
  • https://levelup.gitconnected.com/nominal-typing-in-typescript-c712e7116006
  • https://www.eclipse.org/n4js/features/nominal-and-structural-typing.html

TypeScript 中的类型兼容性相关推荐

  1. TypeScript Type Compatibility 类型兼容性

    官方链接 TypeScript 中的类型兼容性基于结构子类型. 结构类型是一种仅基于其成员关联类型的方法.这与 nominal typing 相反.考虑以下代码: interface Pet {nam ...

  2. TypeScript笔记(4)—— TypeScript中的类型注解

    TypeScript(4):类型注解 [导读]JavaScript是若类型语言,而TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式,为我们提供了静态类型分析能力,这样我们就可以 ...

  3. typescript中的类型type与接口interface

    typescript中的type相当于是给类型起一个新的名字 基本用法: 比如我想声明一个类型为number的年龄age,刚开始学typescript,我们可能会这样写 let age:number ...

  4. 【面试题】 面试官:说说你对 TypeScript 中枚举类型的理解?应用场景?

    一.是什么 枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型 通俗来说,枚举就是一个对象的所有可能取值的集合 在日常生活中也很常见,例如表 ...

  5. 一文讲解Typescript中工具类型

    ​想继续深入Ts其他知识点的同学可以关注下往期文章~

  6. TypeScript中的联合类型、类型别名、接口、类型断言

    一.联合类型 在TypeScript中,联合类型(Union Types)是指用"|"符号将多个类型组合成一个的类型.这种类型可以包含不同的类型,例如字符串.数字或对象.这些不同类 ...

  7. 【进阶】TypeScript 中的 Type

    一 .什么是 TypeScript TypeScript 是静态编程语言 , 是 JavaScript 的超集 简而言之:JavaScript 有的 TypeScript 都有.JavaScript ...

  8. TypeScript Type Innference(类型推断)

    在这一节,我们将介绍TypeScript中的类型推断.我们将会讨论类型推断需要在何处用到以及如何推断. 基础 在TypeScript中,在几个没有明确指定类型注释的地方将会使用类型推断来提供类型信息. ...

  9. typeScript面试必备之-通识七:typeScript中的可索引接口(数组,对象)+类类型接口...

    可索引接口:数组.对象的约束 (不常用) ts定义数组的方式 var arr:number[]=[2342,235325]var arr1:Array<string>=['111','22 ...

最新文章

  1. python opencv 灰度图非局部平均去噪
  2. ASP.NET页面生命周期与应用程序生命周期
  3. Fedora 32正式版今天发布:提供官方下载地址
  4. 建立文件服务器好处,文件服务器好处
  5. js返回上一页并刷新
  6. vue.js分页组件(新手学习记录)
  7. 良好的用户界面设计技巧
  8. 19岁「黑客」连续破解25辆特斯拉
  9. Linux 安装Nginx详细图解教程
  10. (附源码)flutter+React Native+Springboot Api
  11. 密码套件 and 弱密码套件漏洞
  12. 台湾大学林轩田机器学习基石课程学习笔记9 -- Linear Regression
  13. 2007年春节读书心得
  14. mac英文输入模式下不能长按连续输入
  15. 执念斩长河21年Q2生活心得
  16. printf 打印结构体成员函数出错原因分析
  17. 计算机安装Hp1005打印机,hp1005打印机驱动
  18. Swift中隐藏某一页面的返回按钮
  19. 网络设置巨形帧_NAS的巨型帧(Jumbo_Frame)设置对其传输速度的影响的评测与分析...
  20. 水滴宣布完成D轮2.3亿美元融资,AI与大数据是未来投入重点

热门文章

  1. 项目管理计划的制定过程
  2. MATLAB教室人数统计(GUI界面)
  3. RxHttp 让你眼前一亮的Http请求框架
  4. H5直播时疯狂点赞的实现
  5. SAP 常见问题大全及问题解决大全
  6. 天若OCR文字识别 v1.2.0
  7. 错过这15个顶级Python库,你就不算Python程序员
  8. 计算机专业基础综合先看什么,先别学了,你的考研初试科目已经变了!!!
  9. SQL SERVER树状结构排序
  10. 发一些靠谱的招聘网站(含ios)