如何键入具有属性并采用通用参数的函数

时间:2019-09-19 12:52:14

标签: typescript generics typescript-generics

说我有一个具有属性userId的函数,并且userId与它的参数和返回类型之间应该有一些约束。喜欢:

interface IMyfunc<T> {
    (item: T): T
    userId: T
}

它的实现就像:

const func: IMyfunc<number> = a => a
func.userId = 1

但是到目前为止,我的疑问是我现在只能用数字调用func,而不能用字符串或其他名称调用,因为我必须明确绑定使用IMyfunc界面时输入其类型参数。

因此,如果我需要func来获取字符串参数,则必须声明另一个函数,该函数将IMyfunc类型绑定到其type参数上使用string,这使我认为此处的泛型类型失去了真实泛型的含义。

我可以通过某种方式键入具有属性并采用通用参数的函数吗?

2 个答案:

答案 0 :(得分:1)

该问题讨论的是“真实”泛型,我将其解释为任意generic values,TypeScript当前不支持该泛型。 TypeScript具有的唯一通用值是通用函数。


但是,我认为您真正要求的是“类型状态”或“类型突变”,其中值的类型可以根据值的使用方式而改变。我认为意图看起来像这样:

func(4);
const n = func.userId; // n inferred as number
func("a");
const s = func.userId; // s inferred as string
func(false);
const b = func.userId; // b inferred as boolean

...其中func.userId的类型根据先前对func()的调用而变化。

毫无疑问,尽管TypeScript可以通过control flow analysis缩小一些值的类型(所以类型string | number的值可以缩小为类型{{1}的值) }),目前没有任何表示任意类型突变的方法(例如,将类型string的值更改为类型string的值)。

即将到来的asserts feature(计划为TS3.7)应该至少允许在类型系统中表示这种缩小,因此您可以使number缩小{{1} }从类型func(4)func。但是您仍然可能无法进行所需的任意突变。从IMyfunc<unknown>更改为IMyfunc<number>并没有缩小;您首先必须以某种方式扩大到IMyfunc<number>,但我认为没有任何办法可以做到这一点。

comment on the asserts feature pull request中讨论了类似您的用例的问题,而无法扩展的问题是pointed out。也许最终这将是可行的,或者类似的事情?不确定。


在任何情况下,静态类型系统的主要驱动程序之一是表达式具有静态类型,该静态类型表示它可以采用的一组可能的值,并且静态类型不会更改。这与支持无状态和不变性的函数式编程技术很好地配合,并且在命令式编程可能会改变现有值的情况下,通常会返回新值。

如果我们更改代码以执行 操作,则会得到类似以下的内容:

IMyfunc<string>

此处IMyfunc<unknown>是类型为type Func<T> = { userId: T; <U>(item: U): Func<U>; }; 的{​​{1}}类型,但是当您将其作为参数类型为Func<T>的函数调用时,它将返回一个userId。然后,您可以丢弃T并使用新的U(类型为Func<U>的{​​{1}})。

一个可能的实现如下所示:

Func<T>

此实现是无状态的;它永远不会修改自己或传入的项目。您可以这样使用它:

Func<U>

然后您使用userId而不是U

function func<U>(item: U): Func<U> {
  const f = <T>(item: T) => func(item);
  return Object.assign(f, { userId: item });
}

但是,当然,在上述实现中,您可以重用const f = func(4); const n = f.userId; // number console.log(n); // 4 ,因为它是无状态的:

f

在这种情况下,您实际上并不需要所有的复杂性……而您只需实现这样的代码:

func

在这一点上,我已经阐明了这种情况,即您只有一个返回包装对象的函数。我可能会建议您使用此方法,而不要使用任何带有突变的方法,因为它在类型系统中的表现更为出色。但是您比我更了解您的用例。


无论如何,希望能有所帮助;祝好运!

Link to code

答案 1 :(得分:0)

无法通过调用对象上的方法来更改对象的类型(在这种情况下,将值分配给字段)。您可以使用以下内容:

function setUserId<T>(userId: T, func: IMyfunc<unknown>): IMyfunc<T> {
  func.userId = userId;
  return <IMyfunc<T>>func;
}