如何理解ArkTS不支持structural typing

大家好,我是 V 哥,今天有粉丝问 V 哥,ArkTS不支持structural typing 是什么意思?ArkTS不支持接口吗?

V哥把问题整理一下,分享给大家,尤其对TypeScript不熟的小伙伴,看到官网这句表述,是不是也是一头雾水,不知所措,前端小伙伴就不用说了,出门右转吧,因为对你来说,这是个很简单的问题。

首先 关于 structural typing(结构化类型) 是 TypeScript 中的特性,我们先来介绍一下。

TypeScript 中的结构化类型(Structural Typing)特性

TypeScript 是一种静态类型的超集于 JavaScript 的编程语言,它引入了类型系统来增强代码的可维护性和可读性。TypeScript 的类型系统基于结构化类型(Structural Typing),这意味着类型的兼容性和等价性是基于类型实际的结构或定义来确定的,而不是基于其名称或声明位置,这与像 C# 或 C 这样的语言中的命名类型系统(Nominal Typing)不同。

结构化类型的核心原则

  1. 形状兼容性(Shape Compatibility):如果两个类型具有相同的属性和方法,并且这些成员的类型也匹配,那么这两个类型就是兼容的,无论它们的名称是什么。
  2. 允许额外属性(Extra Properties Are Allowed):一个对象可以拥有在类型定义中没有的额外属性,但仍然可以被赋值给该类型。
  3. 参数双变(Parameter Bivariance):对于函数类型,只要函数签名中的其他部分匹配,就可以允许参数类型有额外的特定性或更广泛的通用性。

看个代码案例

基本对象兼容性

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

interface Point3D {
    x: number;
    y: number;
    z: number;
}

const point2D: Point2D = { x: 1, y: 2 };
const point3D: Point3D = { x: 1, y: 2, z: 3 };

// 将 Point3D 赋值给 Point2D
const anotherPoint2D: Point2D = point3D; // 允许,因为 Point3D 包含了 Point2D 所需的所有属性
console.log(anotherPoint2D); // 输出: { x: 1, y: 2, z: 3 }

我们看到在这个例子中,Point3D 包含了 Point2D 所需的所有属性(xy),并且还有一个额外的 z 属性。TypeScript 允许这种赋值,因为它是基于结构的。

函数兼容性

函数类型的兼容性也遵循结构化类型规则,函数的兼容性取决于其参数和返回类型。只要函数签名中的其他部分匹配,就可以允许参数类型有额外的特定性或更广泛的通用性。

type Sum = (a: number, b: number) => number;
const sum: Sum = (a, b) => a + b;

const extendedSum = (a: number, b: number, c: number) => a + b + c;

// 将 extendedSum 赋值给 newSum
const newSum: Sum = (a: number, b: number) => extendedSum(a, b, 0);
console.log(newSum(1, 2)); // 输出: 3

我们看到在这个例子中,extendedSum 函数接受三个参数,但我们只使用了前两个参数来创建一个新的 Sum 类型的函数 newSum。TypeScript 允许这种赋值,因为 newSum 的结构与 Sum 兼容。

结构化类型的优势

  1. 灵活性:能够灵活地分配具有相同结构的不同类型。
  2. 与 JavaScript 的兼容性:TypeScript 的结构化类型非常适合 JavaScript 动态和灵活的特性,使得与现有 JavaScript 代码库的集成更加容易。
  3. 减少样板代码:不需要显式的接口或类型声明,因为兼容性取决于结构,从而减少了样板代码。
  4. 增强代码可重用性:结构化类型允许具有兼容结构的类型可以互换使用,促进了更模块化和可维护的代码。

ArkTS 不支持结构化类型

了解了 TypeScript 是如何支持结构化类型后,咱们就可以很好理解 ArkTS中不支持结构化类型是什么意思了,那在 ArkTS中,不同对象是怎么兼容类型的呢?

在ArkTS中,对象的类型兼容性不是基于对象的结构(如属性和方法的集合)来确定的,而是基于接口或类的名义类型系统。

所以,如果两个对象没有实现相同的接口或继承自相同的类,即使它们具有相同的公共API,它们也被视为完全不同的类型‌。

在ArkTS中,对象的类型兼容性是基于接口或类的名义类型系统(Nominal Typing System),而不是基于对象的结构(如属性和方法的集合)来确定的。这意味着,只有当一个对象的类型与另一个类型完全相同时,它们才被认为是兼容的。这与结构化类型系统(Structural Typing System),如TypeScript中所采用的,有所不同,在结构化类型系统中,如果两个对象具有相同的形状(即相同的属性和方法),它们就被认为是兼容的,即使它们的类型名称不同。

代码案例解释

1. 类型兼容性基于接口或类的声明

在ArkTS中,如果有两个类,即使它们的属性和方法相同,但如果它们没有相同的类声明,它们将不被认为是兼容的。

// 定义两个类,它们具有相同的属性和方法
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    speak() {
        console.log(`The animal says something.`);
    }
}

class Dog {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    speak() {
        console.log(`Woof woof!`);
    }
}

// 在结构化类型系统中,以下赋值是允许的,因为两个对象具有相同的形状
// let animal: Animal = new Dog(); // 错误,在ArkTS中不允许

// 在ArkTS中,上述赋值将导致错误,因为Animal和Dog是不同的类,即使它们具有相同的属性和方法。

2. 接口之间的兼容性

在ArkTS中,接口之间的兼容性也是基于名义类型系统的。即使两个接口具有相同的属性和方法,它们也被认为是不同的类型。

// 定义两个接口,它们具有相同的属性和方法
interface IFly {
    fly(): void;
}

interface IBird {
    fly(): void;
}

// 在结构化类型系统中,以下赋值是允许的,因为两个接口具有相同的形状
// let flyObject: IFly = { fly: () => console.log("Flying") } as IBird; // 错误,在ArkTS中不允许

// 在ArkTS中,上述赋值将导致错误,因为IFly和IBird是不同的接口,即使它们具有相同的方法。

3. 类和接口之间的兼容性

在ArkTS中,类可以实现接口,但类和接口之间的兼容性是基于名义类型系统的。

// 定义一个接口
interface IPrint {
    print(): void;
}

// 定义一个类,实现上述接口
class Printer implements IPrint {
    print(): void {
        console.log("Printing...");
    }
}

// 在ArkTS中,即使Printer实现了IPrint接口,它们也被认为是不同的类型。
// let printObject: IPrint = new Printer(); // 正确

// 但是,如果尝试将IPrint赋值给Printer类型,将导致错误。
// let printerObject: Printer = { print: () => console.log("Printing") } as IPrint; // 错误

再强调一下,在ArkTS中,类型兼容性是基于接口或类的名义类型系统,而不是基于对象的结构。这意味着,只有当两个类型的声明完全相同时,它们才被认为是兼容的。这与TypeScript中基于结构的类型兼容性形成了对比。

那为什么 ArkTS 会不支持结构化类型呢,V 哥的分析应该是考虑以下几个方面:

  1. 潜在的意外兼容性:在结构化类型下,即使概念上不同,类型也可能被认为是兼容的,这可能导致错误。
  2. 有限的反射:TypeScript 没有支持反射,这意味着程序员不能使用结构进行运行时类型检查。
  3. 类型安全与灵活性:结构化类型提供了灵活性,但如果开发者不完全理解其含义,可能会牺牲类型安全。
  4. 调试复杂性:由于类型兼容性的隐式性质,调试可能更加困难,难以追踪和修复问题。
  5. 性能考虑‌:在某些情况下,名义类型系统可能在运行时具有更好的性能,因为它们可以在编译时进行更多的检查‌.

最后

不管官网是基于怎样的考虑,华为的大佬们应该是做充分的思考后的决定,当然也没有说死,后续会根据实际场景和反馈,看否是重新启用结构化类型,好了,到此你应该可以完全理解了,欢迎关注威哥爱编程,鸿蒙开天辟地,你我相伴同行。

posted @ 2024-11-21 17:01  威哥爱编程  阅读(0)  评论(0编辑  收藏  举报