TypeScript 的解决方法不推断逆变?

Workaround for TypeScript not inferring contravaraince?

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> 传递给期望 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 的接口类似于 Setter,除了 set 现在被称为 get 因为虽然它接受一个元素,但它得到一个值(元素是“in”或逆变的东西):

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!
})

请注意,如果我在我的 Property 接口中添加一个 returns E 的方法(并在实例上实现它),typescript 将正确地抱怨案例 1,而不是案例 2。

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

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

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

要禁用此功能并使用逆变参数类型,请使用 strictFunctionTypes 编译器选项,如 in the Typescript docs 所述。请注意,这仅适用于 function 类型,不适用于 method 类型;即使启用了 strictFunctionTypes,方法参数仍然被视为双变量。所以你必须像这样将你的方法声明为函数类型的属性:

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

Playground Link