【问题标题】:Generic Typing for function parameters函数参数的通用类型
【发布时间】:2021-08-25 15:24:25
【问题描述】:

我试图理解一些对我来说看起来很明显的东西,但 Typescript 似乎不允许所有严格的标志都打开(当然有很好的理由)。让我告诉你:

我们都知道:

export interface Basic {
  value: "foo" | "bar" | "baz";
}

export interface Constraint extends Basic {
  value: "foo";
}

有效。但更令人惊讶的是(至少对我而言):

export interface WithFunctionsBasic {
  value: (t: "foo" | "bar" | "baz") => void;
}

export interface WithFunctionsConstraint extends WithFunctionsBasic { // WithFunctionsConstraint extends incorrectly WithFunctionsBasic
  value: (t: "foo") => void;
}

没有。

我们可以将其设为 Generic 以允许我们进行如下行为:

export interface WithFunctionsParametric<T extends Basic> {
  value: (t: T["value"]) => void;
}

const variableConstraint: WithFunctionsParametric<Constraint> = { value: (t: "foo") => {} };
const variableBasic: WithFunctionsParametric<Basic> = { value: (t: "foo" | "bar" | "baz") => {} };

两者都有效。但这有点棘手,因为尽管 Constraint 扩展了 Basic:

const variableConstraint: WithFunctionsParametric<Constraint> = { value: (t: "foo") => {} };
const variableBasic: WithFunctionsParametric<Basic> = variableConstraint; // Not Working

据我了解,如果 Constraint 扩展 Basic(就是这种情况),后者应该可以工作。有人可以解释一下为什么吗?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    您的问题从根本上源于输入和输出在继承方面的行为方式之间的重要区别。

    我们来看第一个例子:

    export interface Basic {
      value: "foo" | "bar" | "baz";
    }
    
    export interface Constraint extends Basic {
      value: "foo";
    }
    

    在这里,Constraint 扩展了 Basic。两者都拥有value 属性,但他们不同意它的类型。

    • Basic 表示它必须是 "foo""bar""baz"。很公平。
    • Constraint 表示只能是"foo"。这是兼容的,因为它是 Basic 的一组有效值的子集。

    换句话说,Constraint 的实例不会破坏Basic 做出的承诺。 value 始终是 "foo""bar""baz" 之一。 Constraint 只会输出一个这一事实是无关紧要的;它仍然承诺返回值来自这三个值的集合。

    现在,我们来看第二个例子:

    export interface WithFunctionsBasic {
      value: (t: "foo" | "bar" | "baz") => void;
    }
    
    export interface WithFunctionsConstraint extends WithFunctionsBasic { // WithFunctionsConstraint extends incorrectly WithFunctionsBasic
      value: (t: "foo") => void;
    }
    

    现在,这是什么意思?好吧,WithFunctionsBasic 做出了一个承诺:“我有一个名为 value 的函数,它接受 "foo""bar""baz"”。

    现在,WithFunctionsConstraint 扩展了 WithFunctionsBasic,这意味着它必须遵守 WithFunctionsBasic 做出的相同承诺。嗯,是吗?

    不,它没有。它的value 函数只接受"foo"。换句话说,我们正在尝试创建一个WithFunctionsBasic 子类型,它打破了WithFunctionsBasic 的含义,因为它的value 函数不能接受"bar""baz"

    对于输出,子类型必须具有相同或更强的限制。进一步限制输出的类型不会破坏超类型的承诺。在形式上,返回类型是协变的;它们可以通过继承变得更加具体。

    另一方面,对于输入,子类型必须具有相同或更弱的限制。也就是说,它必须至少接受相同的输入(以遵守承诺),但也可能允许超类型不允许的其他类型。在形式上,函数参数是逆变的;它们可以通过继承变得不那么具体。

    WithFunctionsConstraint 只接受"foo" 是无效的,因为这意味着WithFunctionsConstraint 不是有效的WithFunctionsBasic。但是,让它接受 "foo""bar""baz""lol""wut" 是完全有效的。

    您在问题中面临的其余问题似乎都是同一基本问题的不同变体。

    【讨论】:

      【解决方案2】:

      不同之处在于您扩展的内容。在您的第一个示例中,很明显,您的 interface Basic 具有 value 属性,该属性是以下 "foo" | "bar" | "baz" 之一。
      您覆盖的Constraint 仍然匹配此接口,因为foo 是上述类型的一部分。

      现在让我们看看你的第二个例子。 value: (t: "foo" | "bar" | "baz") =&gt; void; 的工作方式与您的预期不同。因为您将其定义为具有这三个参数之一的函数。所以你的扩展类不再匹配这个选择器,因为函数定义不匹配。 value: (t: "foo") =&gt; void; 不是 value: (t: "foo" | "bar" | "baz") =&gt; void; 的一部分

      如果你真的想像你建议的那样拆分,请将基本函数拆分为单独的函数调用,每个函数调用一种类型

      export interface WithFunctionsBasic {
        value: ((t: "foo") => void) | ((t: "bar") => void) | ((t: "baz") => void);
      }
      
      export interface WithFunctionsConstraint extends WithFunctionsBasic {
        value: (t: "foo") => void;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-18
        • 2016-01-25
        • 2020-02-06
        • 2013-03-16
        • 2020-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多