解决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"

我希望Test3是"No",因为这也让你这样做(这只是将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 (fine):
new ElementProperty(document.head, {
get(element: Element) { return element.outerHTML.toLowerCase(); } // No prob, every element has outerHTML.
})

Case 2 (bad):

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

请注意,如果我在Property接口中添加一个返回E的方法(并在实例上实现它),typescript将正确地报错case 1,但不会报错case 2。

是否有一个变通的方法,我可以用它来欺骗typescript,以防止我做类似的情况2?我不在乎我是否完全失去了方差,如果可能的话,像这样的东西会很好:

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

默认情况下,Typescript将函数参数类型视为"双变量",这意味着如果一个函数类型的参数类型是超类型另一个函数参数类型的子类型,则该函数类型可以赋值给另一个函数。这是Typescript的行为在设计上不合理的众多特性之一,目的是为了更容易地将现有的Javascript代码库转换为Typescript。

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

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

操场上联系

最新更新