【问题标题】:How to map & index into tuple types that is generically spread in TypeScript?如何映射和索引到通常在 TypeScript 中传播的元组类型?
【发布时间】:2022-01-17 16:55:26
【问题描述】:

TS 4.0 允许传播元组类型和标记元组类型。

我正在尝试使用这两个功能来创建一种带有上下文的函数或括号模式。

这是我的尝试:

type Resource<T = void> = () => Promise<[
  release: () => Promise<void>,
  resource: T
]>;

async function withF<
  Resources extends Array<Resource<unknown>>,
  Result
>(
  resources: Resources,
  f: (...resources: [...Resources]) => Promise<Result>
): Promise<Result> {
  const releases = [];
  const items = [];
  try {
    for (const resource of resources) {
      const [release, item] = await resource();
      releases.push(release);
      items.push(item);
    }
    return await f(...items);
  } finally {
    releases.reverse();
    for (const release of releases) {
      await release();
    }
  }
}

这个想法是你可以像这样使用它:

let count: number = 0;
await withF(
  [
    async () => {
      ++count;
      return [async () => { --count; }, count];
    }
  ],
  async (c: number) => {
    return c;
  }
);

问题是类型不匹配,因为在我的:

  f: (...resources: [...Resources]) => Promise<Result>

Resources 扩展了Array&lt;Resource&lt;unknown&gt;&gt;,我想说fResources 的每个返回类型承诺的第二个元素进行扩展。

第一个挑战是如何将映射类型映射到Resourceshttps://devblogs.microsoft.com/typescript/announcing-typescript-3-1/#mappable-tuple-and-array-types 似乎应该可以。

第二步是应用索引选项。这也应该适用于映射类型。但是我又不知道该怎么做。

理想情况下,我们需要某种类型的构造函数:

  f: (...resources: [...TC<Resources>]) => Promise<Result>

其中TC 是一个特殊的类型构造函数,它将Resources 映射到每个返回类型的第二个元素,并且仍然保留元组长度和顺序。


进一步尝试映射到函数元组:

type Functions = ((...args: any) => unknown)[];
type FunctionReturns<T extends [...Functions]> = { [K in keyof T]: ReturnType<T[K]> };

const fs: Functions = [() => 1, () => 'abc'];

type FsReturns = FunctionReturns<typeof fs>;

无论出于何种原因,即使映射到元组类型的基本能力有效,这里的ReturnType 仍然抱怨,即使我们已经说过T 扩展了一个函数数组。尝试映射到元组类型时,ReturnType 似乎不起作用。

【问题讨论】:

    标签: typescript mapped-types


    【解决方案1】:

    Resources 到它们的类型的映射(你也发现了)可以使用类似于 this answer 的东西来完成,补充说明使用 T extends [U] | U[] 的约束将使编译器推断出 @987654327 的元组@ 代表 T 而不是 U 的数组。

    一旦到位,我们就会遇到打字稿不确定映射类型的结果是否一定是数组的问题。我们可以通过添加与unknown[] 的交集来解决此问题

    type ReturnsOfResources<T extends Resource<any>[]> = {
      [P in keyof T] : T[P] extends Resource<infer R> ? R: never
    }
    
    async function withF<
      Resources extends [Resource<unknown>] | Array<Resource<unknown>>,
      Result
    >(
      resources: Resources,
      f: (...resources: ReturnsOfResources<Resources> & unknown[]) => Promise<Result>
    ): Promise<Result> {
      const releases = [];
      const items = [];
      try {
        for (const resource of resources) {
          const [release, item] = await resource();
          releases.push(release);
          items.push(item);
        }
        return await f(...items as ReturnsOfResources<Resources>);
      } finally {
        releases.reverse();
        for (const release of releases) {
          await release();
        }
      }
    }
    

    Playground Link

    如果你想获得一个使用as const断言的版本,你将不得不更改代码来处理as const生成的只读元组,同样在创建元组时,你需要容器​​元组上的断言以及从资源创建函数返回的元组。

    
    type ReturnsOfResources<T extends readonly Resource<any>[]> = {
      -readonly [P in keyof T]: T[P] extends Resource<infer R> ? R : never
    }
    
    async function withF<
      Resources extends readonly [Resource<unknown>] | readonly Resource<unknown>[],
      Result
    >(
      resources: Resources,
      f: (...resources: ReturnsOfResources<Resources> & unknown[]) => Promise<Result>
    ): Promise<Result> {
      const releases = [];
      const items = [];
      try {
        for (const resource of resources) {
          const [release, item] = await resource();
          releases.push(release);
          items.push(item);
        }
        return await f(...items as any);
      } finally {
        releases.reverse();
        for (const release of releases) {
          await release();
        }
      }
    }
    
    async function x() {
      let count: number = 0;
    
      const resources = [
        async () => {
          ++count;
          return [async () => { --count; }, count] as const;
        },
        async () => {
          return [async () => { }, 'count']  as const;
        }
      ] as const
    
      await withF(
        resources,
        async (c, cs) => {
          return c;
        }
      );
    }
    

    Playground Link

    【讨论】:

    • 其中一个问题是资源获取返回一个promise。所以我们实际上需要使用Awaited来提取内部类型。在这个时间点,playground 链接有效,但在我的 tsc 中无效。
    • 实际上我意识到我根本不需要Awaited。所以没关系。我只需要将类型添加到const releasesconst resources
    • 我注意到的一件事是,与您之前的答案 stackoverflow.com/a/60713409/125734 相比,此处缺少 readonly。想知道这会产生什么影响。
    • 另外,如果我在单独的定义中创建resources,即使我添加了as const,也会出现类型错误。与await withF(resources, async (c, cs) =&gt; { return c; }) 一样,resources 不进行类型检查。
    • @CMCDragonkai 也更新了使用只读元组的答案
    猜你喜欢
    • 2023-03-13
    • 2019-01-11
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 2018-02-05
    • 2023-04-04
    相关资源
    最近更新 更多