为什么TypeScript中的类允许使用duck typing

Iva*_*hko 19 javascript duck-typing interface class typescript

在TypeScript中看起来绝对没问题(从编译器的角度来看)有这样的代码:

class Vehicle {
    public run(): void { console.log('Vehicle.run'); }
}

class Task {
    public run(): void { console.log('Task.run'); }
}

function runTask(t: Task) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle());
Run Code Online (Sandbox Code Playgroud)

但与此同时,我希望编译错误,因为VehicleTask没有任何共同点.

理智的用途可以通过明确的接口定义来实现:

interface Runnable {
    run(): void;
}

class Vehicle implements Runnable {
    public run(): void { console.log('Vehicle.run'); }
}

class Task implements Runnable {
    public run(): void { console.log('Task.run'); }
}

function runRunnable(r: Runnable) {
    r.run();
}

runRunnable(new Task());
runRunnable(new Vehicle());
Run Code Online (Sandbox Code Playgroud)

...或共同的父对象:

class Entity {
    abstract run(): void;
}

class Vehicle extends Entity {
    public run(): void { console.log('Vehicle.run'); }
}

class Task extends Entity {
    public run(): void { console.log('Task.run'); }
}

function runEntity(e: Entity) {
    e.run();
}

runEntity(new Task());
runEntity(new Vehicle());
Run Code Online (Sandbox Code Playgroud)

是的,对于JavaScript来说,拥有这样的行为绝对没问题,因为根本没有类,也没有编译器(只有语法糖),而鸭子打字对于语言来说是很自然的.但TypeScript试图引入静态检查,类,接口等.但在我看来,类实例的鸭子类型看起来相当混乱和容易出错.

Tit*_*mir 19

这是结构类型的工作方式.Typescript有一个结构类型系统,可以最好地模拟Javscript的工作方式.由于Javascript使用duck typing,因此任何定义契约的对象都可以在任何函数中使用.Typescript只是尝试在编译时而不是在运行时验证duck typing.

但是,只有在添加私有类时,您的问题才会显示为普通类,即使它们具有相同的结构,类也会变得不兼容:

class Vehicle {
    private x: string;
    public run(): void { console.log('Vehicle.run'); }
}

class Task {
    private x: string;
    public run(): void { console.log('Task.run'); }
}

function runTask(t: Task) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle()); // Will be a compile time error
Run Code Online (Sandbox Code Playgroud)

此行为还允许您不显式实现接口,例如,您可以为内联参数定义接口,并且任何满足合同的类都将兼容,即使它们没有显式实现任何接口:

function runTask(t: {  run(): void }) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle());
Run Code Online (Sandbox Code Playgroud)

从个人角度来说,来自C#,这一开始看起来很疯狂,但是当谈到可扩展性时,这种类型检查方式可以提供更大的灵活性,一旦你习惯它,你就会看到它的好处.

  • @JonasW.打字稿编译器团队做了类似的事情,这就是我想要添加属性以使类型不兼容的地方.从TS编译器代码:`export type Path = string&{__ pathBrand:any};`https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts (3认同)
  • 只是为了清楚起见:结构类型和鸭子类型或多或少是同一件事,这两者的对比类型系统称为名义类型:https://en.wikipedia.org/wiki/Nominal_type_system (3认同)
  • @ivan 我认为打字稿会从“不可删减的类型”中受益......当你打开一个提案时告诉我,我会支持:) (2认同)
  • @乔纳斯W。我很确定有一个,同时,简单的解决方案是添加一个私有的,你甚至不必使用它,所以没有运行时惩罚,`class Task { private unduckable : true }`就足够了,在运行时“unduckable”将不存在 (2认同)
  • @titian 我知道,但这是一个糟糕的解决方法。我使用例如“type userID = string”、“type groupID = string”来区分两者,因为它们非常相似。如果能阻止鸭子在那里打字就好了。 (2认同)

Lu4*_*Lu4 5

现在可以使用 TypeScript 创建名义类型,允许您根据上下文区分类型。请考虑以下问题:

TypeScript 中的原子类型区分(名义原子类型)

用它的例子:

export type Kilos<T> = T & { readonly discriminator: unique symbol };
export type Pounds<T> = T & { readonly discriminator: unique symbol };

export interface MetricWeight {
    value: Kilos<number>
}

export interface ImperialWeight {
    value: Pounds<number>
}

const wm: MetricWeight = { value: 0 as Kilos<number> }
const wi: ImperialWeight = { value: 0 as Pounds<number> }

wm.value = wi.value;                  // Gives compiler error
wi.value = wi.value * 2;              // Gives compiler error
wm.value = wi.value * 2;              // Gives compiler error
const we: MetricWeight = { value: 0 } // Gives compiler error
Run Code Online (Sandbox Code Playgroud)