我有一个ValueLike<T>类型,它只是值访问的包装器(值可以直接访问,也可以通过数组中的延迟索引视图访问).我还有一个ScalarLike<T>类型,它表示特定TValueLike<T>多个方法.例如,Num类实现了ScalarLike<number>,并且有加法、除法等方法.最后,我有一个VariableLike<T>(=数组)类型,我可以从中访问并推送相应的ScalarLike.

根据我的推理,Num是一个比ScalarLike<number>更窄的类型,因此它应该可以作为参数赋值给VariableLike<number>的Push方法.但是,当我try 这样做时,我得到以下错误:

type ValueLike<T> = {
  value: () => T;
};

class Value<T> implements ValueLike<T> {
  constructor(private val: T) {}
  value = () => this.val;
}

class View<T> implements ValueLike<T> {
  constructor(private array: T[], private index: () => number) {}
  value = () => this.array[this.index()];
}

type ScalarLike<T> = {
  value: () => T;
};

class Num implements ScalarLike<number> {
  constructor(private valueLike: ValueLike<number>) {}
  value = () => this.valueLike.value();
  add = (other: Num) => new Num(new Value(this.value() + other.value()));
  divideBy = (other: Num) => new Num(new Value(this.value() / other.value()));
  // ... other methods
}

type VariableLike<T> = {
  ith: (index: () => number) => ScalarLike<T>;
  push: (scalar: ScalarLike<T>) => number;
};

class NumVariable implements VariableLike<number> {
  constructor(private array: number[]) {}
  ith = (index: () => number) => new Num(new View(this.array, index));
  push = (num: Num) => {   // Error: Type 'ScalarLike<number>' is missing the following properties from type 'Num': valueLike, add, divideBy
    return this.array.push(num.value());
  };
}

(抱歉,代码块很长,但我想,否则可能不清楚我要做的是什么)

你知道我做错了什么吗?

推荐答案

函数的参数是contravariant(请参见the --strictFunctionTypes compiler option15/2887218">Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript),因此它们只接受较窄的类型作为参数是不安全的.如果启用the --strictFunctionTypes compiler option,则对非方法函数类型(函数类型为{a: ()=>void},而方法类型为{a(): void})强制执行此操作.


出于某种原因,关于这tend to involve animals个人的示威,所以我不妨也做同样的事情:

如果我想要Pet分,如果你给我Goldfish分我会很高兴.

interface Pet { name: string; }
interface Goldfish extends Pet { swim(): void; }
declare const goldfish: Goldfish;
const pet: Pet = goldfish; // okay

但如果我想要veterinarian分((pet: Pet) => void分),如果你把我介绍给某种金鱼专科doctor ((gf: Goldfish) => void分),我会得到unhappy分:

type Veterinarian = (pet: Pet) => void;
type GoldfishDoctor = (goldfish: Goldfish) => void;
declare const gfdoc: GoldfishDoctor
let vet: Veterinarian = gfdoc; // error!
// Property 'swim' is missing in type 'Pet' but required in type 'Goldfish'.

如果我碰巧有一条金鱼,也许这不会困扰我,但我没有要求金鱼doctor ,我要求的是兽医.当然,金鱼doctor 不应该宣传自己是一名兽医.当你把你的cat 带到她身边,而她却帮不了你时,她说"但Goldfish分就是Pet分"对她来说并不是什么安慰.

函数不能安全地缩小其参数类型.他们只能安全地widen个人.例如,一家被接受的企业,无论是生病的动物还是肮脏的洗衣店,都是有效的兽医:

interface Laundry { material: string; }
type ComboVetAndDryCleaners = (arg: Pet | Laundry) => void;
declare const comboDoc: ComboVetAndDryCleaners
vet = comboDoc; // okay

所以这就是你有问题的原因.你不能说你接受了全部ScalarLike<number>个,然后只接受了Num个.


当然,人们总是像这样编写不安全的代码而没有问题,因为通常没有人会考虑传递"错误的"较窄的东西.因此,当您使用method syntax而不是function syntax时,即使是--strictFunctionTypes也不会强制执行它.取而代之的是判断bivariantly方法参数,这意味着它们接受安全的加宽但也接受不安全的收窄.因此,这里最方便的"修复"方法是将VariableLike‘S push更改为方法,而不是函数值属性:

type VariableLike<T> = {
  ith: (index: () => number) => ScalarLike<T>;
  push(scalar: ScalarLike<T>): number;
};

这就解决了你的错误.它仍然不安全,但可能在实践中不会有什么问题.

Playground link to code

Typescript相关问答推荐

Typescript自定义键映射

更新:Typescript实用程序函数合并对象键路径

如何防止TypeScript允许重新分配NTFS?

使用带有RxJS主题的服务在Angular中的组件之间传递数据

如何使用WebStorm调试NextJS的TypeScrip服务器端代码?

在实现自定义主题后,Angular 不会更新视图

是否存在类型联合的替代方案,其中包括仅在某些替代方案中存在的可选属性?

React Typescript项目问题有Redux-Toolkit userSlice角色问题

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

跟踪深度路径时按条件提取嵌套类型

创建一个TypeScrip对象并基于来自输入对象的约束定义其类型

如何从接口中省略接口

TypeScrip:使用Cypress测试包含插槽的Vue3组件

顶点堆叠的图表条形图未在正确的x轴上显示

在打印脚本中将对象类型转换为数组

Svelte+EsBuild+Deno-未捕获类型错误:无法读取未定义的属性(读取';$$';)

我是否应该在 Next.js 中的服务器组件中获取秘密数据的所有函数中使用使用服务器?

如何为字符串模板文字创建类型保护

根据索引将元组拆分为两个块

Next.JS-13(应用程序路由)中发现无效的中间件