【问题标题】:How to convert TypeScript discriminated union into an object如何将 TypeScript 可区分联合转换为对象
【发布时间】:2020-08-24 04:31:06
【问题描述】:

说,我有一个类似的结构

//Modified from https://blog.parametricstudios.com/posts/pattern-matching-custom-data-types/.

enum RouteEnum 
{
  Home = "/",
  Todos = "/todos",
  Todo = "/todos/:id",
  NotFound = "*"
}

class HomeLocation
{
  readonly route = RouteEnum.Home;
  
  match<Out>(matcher: LocationMatcher<Out>): Out
  {
    return matcher[RouteEnum.Home](this);
  }
}

// A list of all Todo item links, when one is linked the next route is in TodoLocation.
class TodosLocation
{
  readonly route = RouteEnum.Todos;

  match<Out>(matcher: LocationMatcher<Out>): Out
  {
    return matcher[RouteEnum.Todos](this);
  }
}

// A single Todo location.
class TodoLocation 
{
  readonly route = RouteEnum.Todo;

  match<Out>(matcher: LocationMatcher<Out>): Out
  {
    return matcher[RouteEnum.Todo](this);
  }
}


class NotFoundLocation 
{
  readonly route = RouteEnum.NotFound;

  match<Out>(matcher: LocationMatcher<Out>): Out
  {
    return matcher[RouteEnum.NotFound](this);
  }
}

type NewLocation = HomeLocation | TodosLocation | TodoLocation | NotFoundLocation;

type LocationMatcher<Out> =
{
  [RouteEnum.Home]: (route: HomeLocation) => Out;
  [RouteEnum.Todos]: (route: TodosLocation) => Out;
  [RouteEnum.Todo]: (route: TodoLocation) => Out;
  [RouteEnum.NotFound]: (route: NotFoundLocation) => Out;
};

是否可以将NewLocation 变成类似的结构

// The matching order here is from the the first to latter.
const routes = {
  '/': HomeLocation,
  '/todos': TodosLocation,
  '/todos/:id': TodoLocation,
  '/*': NotFoundLocation
}

如果它很重要,这个问题的更大原因是我正在考虑在这个结构上进行路由匹配,但自然需要一种方法将 URL 转换为那些 *Location 类。

一个例子可能是使用https://github.com/CaptainCodeman/js-router 之类的东西(或者更适合这种结构的类似方法)。例如,这个特定的库在类似routes 示例的结构上运行。我认为,即使不使用这个特定的库,知道如何枚举有区别的联合并创建一个新类型也是很好的了解信息。

目标是将解析的参数作为强类型。当然,这是手动的,但他们需要首先从*Location 类中的相应路由以一种或另一种方式进行匹配。一种选择是为每个 *Location 类设置一个回调函数,该函数从匹配的路由中获取输入。

编辑 我写了一个 StackBlitz 来更好地说明我的思考。在https://stackblitz.com/edit/typescript-hl6drb?file=index.ts

【问题讨论】:

  • '/article/*Todo 是错别字吗?他们应该分别是'*'TodoLocation 吗?
  • 啊,是的。我在写这篇文章时从引用的js-router 复制了最后一部分,并思考如何在我的 n00b TS 知识中弥合这种“枚举歧视联合”的差距。我编辑文本,只需几秒钟。
  • 问题标题也让我感到困惑。我没有看到问题正文中任何地方提到的元组数组。或者任何数组或任何元组,就此而言。
  • 这一定是我的n00b TS技能。我的意思是const routes = { 部分,现在我想到它是一个对象。我还认为,与您的快速回答有关,我的问题是将NewLocation 转换为像const routes = { 这样的运行时结构,这样我就可以轻松使用js-router 库,而无需多次编写字符串,或者可能使用该库代码并对其进行重构以直接处理该结构。
  • 所以“运行时结构”是指您希望将 type NewLocation 转换为发出 JavaScript 的东西?答案是“你不能那样做”;这违反了 TS 设计目标(具体来说,它是non-goal #5)。 TS 的类型系统旨在描述,而不是发出 JS。你可以做相反的事情;编写你的运行时结构,然后为它生成一个类型。

标签: typescript


【解决方案1】:

如果您想以编程方式生成一个对象类型,其中键来自NewLocation 联合成员的route 属性,而值是NewLocation 的相应元素的构造函数,那么您可以这样做这个:

type RoutesType = { 
  [K in NewLocation['route']]: new () => Extract<NewLocation, { route: K }> 
};

计算结果相当于

type RoutesType = {
    "/": new () => HomeLocation;
    "/todos": new () => TodosLocation;
    "/todos/:id": new () => TodoLocation;
    "*": new () => NotFoundLocation;
}

然后您可以将routes 变量注释为该类型:

const routes: RoutesType = {
  '/': HomeLocation,
  '/todos': TodosLocation,
  '/todos/:id': TodoLocation,
  '*': NotFoundLocation
}

请注意,这与

const routes: RoutesType = {
  [RouteEnum.Home]: HomeLocation,
  [RouteEnum.Todos]: TodosLocation,
  [RouteEnum.Todo]: TodoLocation,
  [RouteEnum.NotFound]: NotFoundLocation
}

所以你可以随意写。

这对你有用吗?希望有帮助;祝你好运!

Playground link


还有像 this 这样的东西,这是我能从值到类型的最接近的东西......

【讨论】:

  • 让我检查一下,我刚刚开始了 Stackblitz。 :)
  • 回到这一点,我认为stackblitz.com/edit/typescript-hl6drb?file=index.tsconst routeDefinitions2 = { '/': HomeLocation, '/todos': TodosLocation, '/todos/:id': TodoLocation, '*': NotFoundLocation } 这样的写作几乎可以工作。虽然然后它首先在枚举中写入路由两次,然后在路由定义中写入(我想我可以提供一个函数作为第二个参数,然后可以new 或其中一个类)。
  • 也许我错过了你所说的“写两次”的意思,因为你一直在写,例如,RouteEnum.Home 到处都是。哦,让我快速更改一下答案(根本不会改变类型),看看您是否觉得不同:
  • 我尝试在 TS 上进行模式匹配,但我仍在学习。我已经尝试了各种各样的事情,blog.parametricstudios.com/posts/… 是最接近的(我明白一些/足够了)。让我看看那个,看起来最新的应该可以工作。我知道(现在你写了它)不可能采用type NewLocation 并枚举它来创建类似的结构,因此替代方案是为匹配编写两次路由(RouteEnum+routes)或以某种方式更改类型。我认为这比写两次纯字符串要好。但是让我检查一下...
  • 快速查看我添加到此答案底部的操场链接(链接显示“this”),以了解从您的课程到您的类型的可能方式......我基本上得到了完全摆脱枚举。也许它会给你一些想法。再次祝你好运!
猜你喜欢
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
  • 2021-06-20
  • 2016-09-14
  • 2014-05-17
相关资源
最近更新 更多