TypeScript Add named type arguments

j7dteeu8  于 6个月前  发布在  TypeScript
关注(0)|答案(8)|浏览(51)

搜索词

泛型,类型参数,命名参数,命名类型参数,类型参数,命名类型参数

建议

应该能够通过名称而不是位置将类型参数传递给泛型,例如:

interface Foo<T = SomeDefaultType, U> { ... }

// Current syntax
const foo: Foo<SomeDefaultType, string> ...

// Proposed syntax
const foo: Foo<U = string> ...
// yields foo: Foo<SomeDefaultValue, string>

这个概念松散地受到了Python的命名参数的启发:

def foo(bar = "I'm bar", baz):
    ...

foo(baz="I'm baz")

使用场景

泛型只接受位置类型参数。如果你有一个接受许多类型参数的泛型,其中大多数或所有都有默认值,例如:

interface Handler<Type = string, TPayload = object, TOutput = void> {
  type: Type
  handle(payload: TPayload): TOutput
}

假设我们有一个实现了Handler接口的类,但是对于TypeTPayload的默认值对我们来说是可以接受的,我们只想为TOuput指定一个类型,目前必须为TypeTPayload传递类型参数:

class Foo implements Handler<string, object, Promise<number>>

如果可以通过名称传递类型参数,我们可以使用更简洁的形式:

class Foo implements Handler<TOutput=Promise<number>>

示例

Fastify 在许多具有默认值的参数上公开了泛型类型,例如:

interface FastifyRequest<
    HttpRequest = http.IncomingMessage,
    Query = DefaultQuery,
    Params = DefaultParams,
    Headers = DefaultHeaders,
    Body = DefaultBody
  > { ...

使用提议的语法,我们可以创建具有更少开销的专业接口,例如:

import * as fastify from 'fastify';

const app = fastify();
app.get('/users/:id', async (req: FastifyRequest<Params = {id: string }>, res: FastifyReply) => {
   // req.params is strongly typed now
})

检查清单

我的建议满足以下准则:

  • 这不会对现有的TypeScript/JavaScript代码造成破坏性的变化
  • 这不会改变现有JavaScript代码的运行时行为
  • 这可以在不根据表达式的类型发出不同的JS的情况下实现
  • 这不是一个运行时特性(例如库功能、JavaScript输出的非ECMAScript语法等)
  • 这个特性将与 TypeScript's Design Goals 的其他部分保持一致。
z3yyvxxp

z3yyvxxp1#

参数是在使用站点中输入的内容;而参数是在声明站点中设置的内容。

w3nuxt5m

w3nuxt5m2#

参数是在声明处的,而参数是在使用处的。
感谢澄清,我总是把这些术语混淆在一起。我更新了描述以正确使用它们

yzuktlbb

yzuktlbb3#

这是一个重复的#23696,并且自此以后已经被#26349取代,因为它不会遇到这样的问题。

hfsqlsce

hfsqlsce4#

@Airblader 弱化提案如何取代强有力的提案?(可选顺序类型参数)与(命名类型参数)。我不认为#26346中的问题是该想法固有的。

yebdmbv4

yebdmbv45#

你可以称之为一种替代方法,而不是取代方法,如果你愿意的话,但这就是@weswigham在关闭问题时所说的。无论如何,这是一个重复的问题。
在这里提出的问题中的用例同样可以用取代/替代方法解决,所以它是一个有效的替换。相反,它不会引发诸如类型参数命名成为公共API等问题。

eagi6jfj

eagi6jfj6#

@Airblader

OP的目标是让声明更简洁,这确实可以通过pr #26349实现,但我认为还有另一种用途,那就是向读者提供清晰的信息,就像函数的命名参数一样。

我宁愿这样说:
(1)

interface A extends B<Indexer=string, Counter=number, Timestamper=Date> {}

而不是这样说:
(2)

interface A extends B<string, number, Date> {}

在第二种情况下,读者是否知道B中的字符串是用于什么?数字还是日期?(编造的例子)

jgzswidk

jgzswidk7#

在除了上面展示的例子之外,还有一种情况是用户想要使用其中的一个参数,但不是全部。例如在 express 中有一个接口:

interface Request<
        P = core.ParamsDictionary,
        ResBody = any,
        ReqBody = any,
        ReqQuery = core.Query,
        Locals extends Record<string, any> = Record<string, any>
    > extends core.Request<P, ResBody, ReqBody, ReqQuery, Locals> {}

想要覆盖其中某一个参数是很合理的,比如 ReqBody,在其中我希望能够实现:

app.get("/", (req: Request<ReqBody = { foo: string, bar: boolean }>, res) => {
  // it knows it's a string and must exist
  const foo = req.body.foo;
});
fhity93d

fhity93d8#

在OP中,pedrolcn提议命名类型参数将允许最终用户更轻松地示例化具有许多默认参数的泛型对象。然而,我认为命名类型参数具有完全不同的用途 - 用于文档。考虑以下情况。
在TypeScript 4.0之前,以下代码是常见的:

/** The first element is the index of the foo array, the second element is the index of the bar array. */
type MyTuple = [number, number];

这并不理想,因为我们被迫编写注解来解释代码的作用。TypeScript 4.0通过允许我们使用命名元组功能编写更具表现力的代码来解决这个问题:

type MyTuple = [fooArrayIndex: number, barArrayIndex: number];

好多了!不需要注解。
接下来,让我们考虑Map的情况:

/** The map keys are foo array indexes, the map values are bar array indexes. */
const myMap = new Map<number, number>();

与前面的例子的相似之处应该很明显。理想情况下,我们应该以与元组相同的方式重构掉这个JSDoc注解。但是怎么做呢?对于泛型类型参数,没有类似的“命名元组”功能。
我们真正想要做的是使用与命名元组相同的冒号语法编写这段代码:

const myMap = new Map<fooArrayIndex: number, barArrayIndex: number>();

或者使用pedrolcn在OP中提议的等于语法:

const myMap = new Map<fooArrayIndex = number, barArrayIndex = number>();

无论如何,想法是别名应该在有人在VSCode中将鼠标悬停在Map上时显示出来,使代码自记录。

相关问题