TypeScript 不推断矛盾的解决方法?

Jua*_*uan 2 covariance contravariance generic-variance typescript

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"
Run Code Online (Sandbox Code Playgroud)

我希望是“否”,因为这也让你可以这样做(这与传递给需要 a 的函数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!
Run Code Online (Sandbox Code Playgroud)

在我的具体情况下,我正在编写一些从 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) }
}
Run Code Online (Sandbox Code Playgroud)

情况1(罚款):

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

情况2(不好):

const element: Element = document.body;

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

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

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

class ElementProperty<E extends Element, P === Property<E>> { ... }
Run Code Online (Sandbox Code Playgroud)

kay*_*ya3 6

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

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

interface Getter<E> { get: () => E }
interface Setter<E> { set: (value: E) => void }
Run Code Online (Sandbox Code Playgroud)

游乐场链接