简体   繁体   English

如何为 TypeScript 中的泛型类型编写泛型辅助函数?

[英]How to write generic helper functions for a generic type in TypeScript?

I'm having trouble understanding the interplay of TypeScript generics with defaults, and how to keep utility functions as widely constrained as possible.我无法理解 TypeScript generics 与默认值的相互作用,以及如何尽可能广泛地限制实用程序功能。 (This example will be contrived, but it makes it easy to show the issue in a small amount of code.) (这个例子是人为的,但它可以很容易地用少量的代码来显示问题。)

In modeling a simple case where you have an "editor" of a given "value", you might express it like:在建模一个简单的案例时,你有一个给定“值”的“编辑器”,你可以这样表达:

type Editor<V extends object = object> = {
    value: V
    set: (value: V) => void
}

The editor is generic so that you can further refine the type of the value.编辑器是通用的,因此您可以进一步细化值的类型。 And I've added the default value to the generic argument so that you can more easily omit it for cases where you don't need to make it more restrictive.而且我已将默认值添加到通用参数,以便您可以更轻松地在不需要使其更具限制性的情况下省略它。

Then you have an external helper function written to be generic to work with any editor (simplified):然后你有一个外部助手 function 被写成通用的,可以与任何编辑器一起使用(简化):

const get = <E extends Editor>(editor: E): E['value'] => {
    return editor.value
}

But then in trying to write a factory for these editors, I run in to a TypeScript error:但是在尝试为这些编辑器编写工厂时,我遇到了 TypeScript 错误:

const create = <V extends object = object>(value: V): Editor<V> => {
    const editor: Editor<V> = {
        value,
        set: (value: V) => {
            const prev = get(editor)
            // ...
        }
    }

    return editor
}

The error is:错误是:

Argument of type 'Editor<V>' is not assignable to parameter of type 'Editor<object>'.
  Types of property 'set' are incompatible.
    Type '(value: V) => void' is not assignable to type '(value: object) => void'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'object' is not assignable to type 'V'.
          'object' is assignable to the constraint of type 'V', but 'V' could be instantiated with a different subtype of constraint 'object'.(2345)

Why is that happening?为什么会这样? It's confusing because get seems be as lenient as possible, so I'd assume it would cover all subtypes, but still TypeScript complains that the types might have a mismatch.这令人困惑,因为get似乎尽可能宽松,所以我认为它会涵盖所有子类型,但 TypeScript 仍然抱怨类型可能不匹配。

Here's a TypeScript Playground link .这是TypeScript 游乐场链接

The issue of the original playground is solved in this playground but I don't know if I've removed the feature you were exploring.原始游乐场的问题在这个游乐场中得到了解决,但我不知道我是否删除了您正在探索的功能。

This code compiles and I think would be a basis for doing stuff...这段代码可以编译,我认为这将是做事的基础......

type Editor<V extends object> = {
  value: V;
  set: (value: V) => void;
};

const get = <V extends object>(editor: Editor<V>): V => {
  return editor.value;
};

const create = <V extends object>(value: V): Editor<V> => {
  const editor: Editor<V> = {
    value,
    set: (value: V) => {
      const prev = get(editor);
      // ...
    },
  };

  return editor;
};

To illustrate the problem, you can recreate the original error from my working example by following along with these step by step changes in the playground.为了说明问题,您可以通过在操场上进行这些逐步更改来重新创建我的工作示例中的原始错误。

First change the signature of get to this, which introduces a potentially narrowing type E...首先将get的签名更改为此,它引入了一个潜在的缩小类型 E...

const get = <V extends object, E extends Editor<V>>(editor: E): V => {
  return editor.value;
};

Then fix it by ensuring that the narrowing type is in fact exactly the type of the editor instance...然后通过确保缩小类型实际上正是编辑器实例的类型来修复它......

const prev = get<V,typeof editor>(editor);

...which clarifies that there was an additional loose type flying around that needed nailing down, compared to my working code which didn't have it. ...这澄清了与我没有它的工作代码相比,有一个额外的松散类型需要确定。

Regards open questions about the interplay between Generics and Defaults and such you're not the only one.关于 Generics 和 Defaults 之间相互作用的未解决问题,因此您不是唯一一个。 I have some heuristics and learnings...我有一些启发和学习...

A) Where a type is part of the composition of an argument, always include the type in the function definitions which include that argument, eliminate defaults everywhere and never default to any . A)如果类型是参数组合的一部分,请始终在包含该参数的 function 定义中包含该类型,消除任何地方的默认值,并且永远不要默认为any

B) In practice I've found that Typescript almost basically always infers the types from the argument anyway, but only if they have been preserved by following A) such that they are present in the code path for Typescript to inspect. B)在实践中,我发现 Typescript 几乎总是从参数中推断出类型,但前提是它们已通过遵循 A)保留,以便它们存在于 Typescript 的代码路径中进行检查。

This probably explains why my first interventions in your code was removing the defaults, the second was ensuring that any reference to Editor was always accompanied with a Generic V which could be inferred from it.这可能解释了为什么我对您的代码的第一次干预是删除默认值,第二次是确保对 Editor 的任何引用始终伴随着可以从中推断出的 Generic V。

Oh and C) if a type doesn't need to be aliased (and potentially narrowed), don't add the narrowing 'alias', as you may create an alias that collides later.哦,C)如果一个类型不需要别名(并且可能缩小),请不要添加缩小的“别名”,因为您可能会创建一个稍后发生冲突的别名。

So for example if V is all that's needed to be able to define a function signature passing an Editor, then don't create an alias E extends Editor<V> , or worse E extends Editor such that later signatures could unexpectedly introduce what amounts to an alias E2 extends Editor<V2> which may or may not be the same thing.因此,例如,如果 V 是能够定义传递编辑器的 function 签名所需的全部内容,那么不要创建别名E extends Editor<V> ,或者更糟糕的是E extends Editor ,这样以后的签名可能会意外地引入相当于别名E2 extends Editor<V2>可能是也可能不是同一件事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM