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

How to type a function that have properties and takes generic parameters

假设我有一个具有 属性 userId 的函数,并且 userId 和它的参数以及 return 类型之间应该有一些约束。喜欢:

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

它的实现是这样的:

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

到目前为止一切顺利,但是,我怀疑我现在只能用数字调用 func 而不能用字符串或其他东西调用它,因为我 必须 当我使用 IMyfunc 接口时显式绑定它的类型参数。

因此,如果我需要 func 来获取字符串参数,我必须声明另一个使用 IMyfuncstring 类型绑定到其类型参数的函数表达式,这让我想到这里的泛型失去了真正的泛型.

的意义

有什么方法可以键入具有属性并采用通用参数的函数吗?

您无法通过调用对象的方法来更改对象的类型(在您的情况下为字段赋值)。不过,您可以使用以下内容:

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

问题涉及 "real" 泛型,我将其解释为任意 generic values,目前 TypeScript 不支持它们。 TypeScript 唯一的泛型值是泛型函数。


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

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 类型的值可以缩小为类型 string),目前没有表示 任意 类型突变的方法(例如,类型 string 的值被更改为类型 [=22= 的值) ]).

即将推出的 asserts feature (slated for TS3.7) 至少应该允许在类型系统中表示这种缩小,因此您可以从类型 [=26] 中缩小 func(4) func =] 到 IMyfunc<number>。但是您仍然可能无法进行您正在寻找的任意突变。从 IMyfunc<number> 更改为 IMyfunc<string> 并没有缩小;您首先必须以某种方式扩大到 IMyfunc<unknown>,我认为没有任何方法可以做到这一点。

comment on the asserts feature pull request, and the can't-widen-back problem was pointed out 中讨论了类似您的用例的内容。也许最终这将是可行的,或者类似的东西?不确定。


无论如何,静态类型系统的主要驱动因素之一是表达式具有静态类型,表示它可以采用的一组可能值,并且静态类型不会改变。这适用于支持无状态和不变性的函数式编程技术,并且通常 return 在命令式编程可能改变现有值的情况下的新值。

如果我们更改代码以执行 ,您将得到更类似于以下内容的内容:

type Func<T> = {
  userId: T;
  <U>(item: U): Func<U>;
};

这里的 Func<T> 是一个带有 userId 类型 T 的类型,但是当你将它作为一个带有 U 类型参数的函数调用时,它return 是 Func<U>。然后您可以丢弃 Func<T> 并使用新的 Func<U>(它有一个类型为 UuserId)。

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

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:

const g = f("a");
const s = g.userId; // string
console.log(s); // 1

当然,在上面的实现中,您可以重用func(),因为它是无状态的:

const h = func(false);
const b = h.userId; // boolean
console.log(b); // false

在这种情况下,您真的不需要所有这些复杂性...您只需实现如下代码:

function func<U>(item: U): { userId: U } {
  return { userId: item };
}

const f = func(4);
const n = f.userId; // 4
console.log(n); // 4

const g = func("a");
const s = g.userId; // string
console.log(s); // 1

const h = func(false);
const b = h.userId; // boolean
console.log(b); // false

在这一点上,我已经阐明了您刚刚获得 return 是一个包装对象的函数的程度。我可能会推荐这个而不是任何带有突变的东西,因为它更适合类型系统。但是你比我更了解你的用例。


总之,希望对你有所帮助;祝你好运!

Link to code