简体   繁体   English

TypeScript 不兼容的函数签名

[英]TypeScript incompatible function signatures

The following code is just an example to reproduce an error that I don't understand.以下代码只是重现我不明白的错误的示例。
In setFilter I have narrowed the type of T to Product , so I would expect to be able to set filter successfully, because T and Product have the same subtypes.setFilter ,我将T的类型缩小到Product ,因此我希望能够成功设置filter ,因为TProduct具有相同的子类型。

How is it true that: 'T' could be instantiated with a different subtype of constraint 'Product'.事实如何: 'T' could be instantiated with a different subtype of constraint 'Product'. ? ?

EDIT: I realised that you can pass a subtype into setFilter that would make these signatures incompatible... so how could I define filter to allow this pattern?编辑:我意识到你可以将一个子类型传递给setFilter ,这会使这些签名不兼容......那么我怎么能定义filter来允许这种模式呢?

type Product = { type: string };

let filter: (product: Product) => boolean = () => true;

function setFilter<T extends Product>(productType: string) {
  filter = (product: T) => product.type === productType;
  /* Type '(product: T) => boolean' is not assignable to type '(product: Product) => boolean'.
    Types of parameters 'product' and 'product' are incompatible.
    Type 'Product' is not assignable to type 'T'.
    'Product' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Product'.
  */
}

const res = [{ type: 'subscription' }, { type: 'hardware' }, { type: 'hardware' }];
setFilter('hardware');

console.log(res.filter(filter));

This works for me, just remove (:T):这对我有用,只需删除(:T):

type Product = { type: string };
let filter: (product: Product) => boolean = () => true;
function setFilter<T extends Product>(productType: string) {

filter = (product) => product.type === productType;
}

const res = [{ type: 'subscription' }, { type: 'hardware' }, { type: 
'hardware' }];
setFilter('hardware');

console.log(res.filter(filter));

The problem is that when you define T extends Product then TypeScript can't guarantee that T would be correct.问题在于,当您定义T extends Product TypeScript 无法保证T是正确的。 Here is a minimal example, let's say we have:这是一个最小的例子,假设我们有:

type Product = { type: string };

interface SubProductA extends Product {
  name: string
}

interface SubProductB extends Product {
  rating: number
}

declare let filter: (product: Product) => boolean

const product1: Product = {
  type: "foo"
}

const product2: SubProductA = {
  type: "bar",
  name: "Fred"
}

const product3: SubProductB = {
  type: "baz",
  rating: 5
}

filter(product1); //valid! it's a Product. But not SubProductA
filter(product2); //valid! it's a Product *and* SubProductA
filter(product2); //valid! it's a Product. But not SubProductA

See on TypeScript Playground 请参阅 TypeScript Playground

The filter expects a function that only expects Product , so calling it with the parent type is fine - that's guaranteed to work. filter需要一个只需要Product的函数,所以用父类型调用它是可以的- 这保证可以工作。 You can also call it with a subtype - that's also fine since it's assignable to its parent.您也可以使用子类型调用它 - 这也很好,因为它可以分配给它的父类型。 However, you cannot guarantee that filter will be called with a specific subtype.但是,您不能保证将使用特定子类型调用filter Just to illustrate this as clearly as possible:只是为了尽可能清楚地说明这一点:

type Animal = { };

interface Cat extends Animal {
  cute: number
}

interface Dog extends Animal {
  friendly: number
}

function getCatsAndDogs(): Array<Cat | Dog> {
  return []; //dummy implementation
}

const dogFilter: (x: Dog) => boolean = x => x.friendly > 9;
const catFilter: (x: Cat) => boolean = x => x.cute > 9;

let arr: Array<Animal> = getCatsAndDogs(); //valid (Cat | Dog) is assignable to Animal
arr.filter(dogFilter); //not valid - we cannot guarantee Animal would be Dog
arr.filter(catFilter); //not valid - we cannot guarantee Animal would be Cat

See on TypeScript Playground 请参阅 TypeScript Playground

So, if you intend to only use the common parent, so you don't depend on anything defined in a subtype, then you don't need a generic, just use the parent type:因此,如果您打算只使用公共父类型,因此不依赖于子类型中定义的任何内容,那么您不需要泛型,只需使用父类型:

function setFilter(productType: string) {
  filter = (product: Product) => product.type === productType;
}

This works because every subtype of Product is a product but not every Product is a specific subtype.这是有效的,因为Product每个子类型都是一个产品,但不是每个Product都是一个特定的子类型。 The same way as every Cat is an Animal but not every Animal is a Cat .就像每Cat都是Animal但不是每只Animal都是Cat

If you do need to use a subtype for filtering, you can get around it using a type assertion which will look something like this:如果您确实需要使用子类型进行过滤,则可以使用类型断言来解决它,该类型断言如下所示:

filter = (p: Product) => (g as SubTypeA).name === "Fred";

Or if using a generic argument T then:或者,如果使用通用参数T则:

filter = (p: Product) => (p as T)/* include something T specific not applicable for Product*/;

This is known as downcasting because you convert from the parent/super type to the child/sub type.这称为向下转换,因为您从父/超类型转换为子/子类型。

However, as the compiler already said, type safety cannot be guaranteed at compile time.但是,正如编译器已经说过的,在编译时不能保证类型安全。 Unless you have information the compiler doesn't and you're sure that filter will actually be called with a specific subtype, then you shouldn't just use a straight type assertion there.除非你有编译器没有的信息,并且你确定filter实际上会用特定的子类型调用,否则你不应该只在那里使用直接类型断言。 To guarantee type safety, you can use a type guard :为了保证类型安全,您可以使用类型保护

type Product = { type: string };

interface SubProductA extends Product {
  name: string
}

interface SubProductB extends Product {
  rating: number
}

function isSubProductA(product: Product): product is SubProductA {
  return "name" in product;
}

declare let filter: (product: Product) => boolean

filter = (product: Product) => {
  if (isSubProductA(product)) {
    //no need to do type assertion we're sure of the type
    return product.name === "Fred";
  }

  return false;
}

See on TypeScript Playground 请参阅 TypeScript Playground

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM