1

TypeScript 似乎没有推断出逆变性。这是一个说明不一致的示例:

class Base { base = "I'm base" }
class Der extends Base { der = "I'm der" }

interface Getter<E> { get(): E }
interface Setter<E> { set(value: E): void }

type Test1 = Getter<Der> extends Getter<Base> ? 'Yes' : 'No' // "Yes"
type Test2 = Getter<Base> extends Getter<Der> ? 'Yes' : 'No' // "No"

type Test3 = Setter<Der> extends Setter<Base> ? 'Yes' : 'No' // "Yes"
type Test4 = Setter<Base> extends Setter<Der> ? 'Yes' : 'No' // "Yes"

我希望Test3是“否”,因为这也让你这样做(这只是传递Getter<Base>给期望 a 的函数的反面Getter<Der>):

const setBase = (setter: Setter<Base>) => setter.set(new Base()) 

const derSetter = {
    set: (thing: Der) => console.log(thing.der.toLowerCase())
}

setBase(derSetter); // Cannot read property 'toLowerCase' of undefined!

在我的具体情况下,我正在编写一些从 HTML 元素中读取值的代码,因此有一个名为的接口Property类似于in”或逆变的东西):Settersetget

interface Property<E extends Element> {
    get(element: E): string
}

class ElementProperty<E extends Element, P extends Property<E>> {
    constructor(
        private readonly element: E, 
        private readonly property: P
    ) { }

    get value() { return this.property.get(this.element) }
}

案例1(罚款):

new ElementProperty(document.head, {
    get(element: Element) { return element.outerHTML.toLowerCase(); } // No prob, every element has outerHTML.
})

案例2(坏):

const element: Element = document.body;

new ElementProperty(element, {
    get(element: HTMLLinkElement) { return element.href.toLowerCase(); } // The body doesn't have an href!
})

请注意,如果我在我的界面中添加一个返回 E 的方法Property(并在实例上实现它),打字稿将正确地抱怨案例 1,但不会抱怨案例 2。

是否有一种解决方法可以用来欺骗打字稿以阻止我做类似案例 2 的事情?我不在乎我是否完全失去方差,如果可能的话,这样的事情会很好:

class ElementProperty<E extends Element, P === Property<E>> { ... }
4

1 回答 1

4

默认情况下,Typescript 将函数参数类型视为“双变量”,这意味着如果函数类型的参数类型是另一个参数类型的超类型子类型,则它可以分配给另一个函数类型。这是 Typescript 的行为在设计上不合理的各种功能之一,以便更容易地将现有的 Javascript 代码库转换为 Typescript。

要禁用此功能并具有逆变参数类型,请使用Typescript 文档中strictFunctionTypes描述的编译器选项。请注意,这仅适用于函数类型,不适用于方法类型;即使启用,方法参数仍被视为双变量。所以你必须将你的方法声明为函数类型的属性,如下所示:strictFunctionTypes

interface Getter<E> { get: () => E }
interface Setter<E> { set: (value: E) => void }

游乐场链接

于 2021-08-21T14:06:03.403 回答