【问题标题】:Generic constraint between two properties of an object对象的两个属性之间的通用约束
【发布时间】:2022-01-29 04:22:55
【问题描述】:

我正在尝试改进前端路由配置对象的类型。我正在尝试将pathprepare 字段绑定在一起,以便prepare 函数字段根据路径中的:keys 接收适当的参数。

配置中的每个路由项(为简洁起见省略其他字段)看起来像这样:

interface Routes<Path extends string> {
    path: Path;
    prepare?: (params: PathArgs<Path>) => object;
    routes?: Routes[];
}

用于从路径字符串中提取参数的助手是

type PathParams<
  Path extends string
> = Path extends `:${infer Param}/${infer Rest}`
  ? Param | PathParams<Rest>
  : Path extends `:${infer Param}`
  ? Param
  : Path extends `${infer _Prefix}:${infer Rest}`
  ? PathParams<`:${Rest}`>
  : never;

type PathArgs<Path extends string> = { [K in PathParams<Path>]: string };

// { siteId: string }
type x = PathArgs<`/dashboard/sites/:siteId`>

理想情况下,如果我做了类似的事情

const routes: Routes[] = [
  {
    path: `/dashboard/:siteId`,
    prepared: (params) => {...},
    routes: [
      { 
        path: `/dashboard/:siteId/widgets/:widgetId`,
        prepared: (params) => {...}
      },
      {
        path: `/dashboard/:siteId/friend/:friendId`,
        prepared: (params) => {...}
      }
    ]
  }
]

params 的类型将自动在第一条路由中被识别为 {siteId: string}{siteId: string, widgetId: string} 用于第二条路由,{siteId: string, friendId: string} 用于第二条路由。

我上面对Routes 的类型声明约束路径并为单个对象正确准备字段,但不处理配置对象的递归性质,因为每个嵌套路由都可以是不同的泛型,因为每个路径是唯一的。我想知道这在 TypeScript 中是否可行。

这是一个playground,包含上面的代码

【问题讨论】:

  • 请提供minimal reproducible example,清楚地表明您面临的问题。理想情况下,有人可以将代码粘贴到像 The TypeScript Playground (link here!) 这样的独立 IDE 中,然后立即着手解决问题,而无需首先重新创建它。所以应该没有伪代码、拼写错误、不相关的错误或未声明的类型或值。

标签: typescript


【解决方案1】:

这里最简单的解决方案是创建builder函数并创建类似于链表数据结构的东西。 考虑这个例子:

interface Route<Path extends string> {
  path: Path;
  prepare(params: PathArgs<Path>): void;
}

type PathParams<
  Path extends string
  > = Path extends `:${infer Param}/${infer Rest}`
  ? Param | PathParams<Rest>
  : Path extends `:${infer Param}`
  ? Param
  : Path extends `${infer _Prefix}:${infer Rest}`
  ? PathParams<`:${Rest}`>
  : never;

type PathArgs<Path extends string> = { [K in PathParams<Path>]: string };

type ValidRoute = `/${string}/:${string}`

const route = <
  Path extends string,
  Routes extends Route<`${Path}${ValidRoute}`>[]
>(
  path: Path,
  prepare: (param: PathArgs<Path>) => void,
  ...routes: Routes
) => ({
  path,
  prepare,
  routes
})

const routes = <
  Str extends string,
  Elem extends Route<Str>[]
>(...elems: [...Elem]) =>
  elems

const result = [
  route(`/dashboard/:siteId`, (arg) => { },
    route(`/dashboard/:siteId/friend/:friendId`, (arg) => { }),
    route(`/dashboard/:siteId/widgets/:widgetId`, (arg) => { },)
  ),
  route(`/menu/:optioId`, (arg) => { },
    route(`/menu/:optioId/select/:selectId`, (arg) => { })
  )
]

Playground

Arrl arg 参数被正确推断。无需创建复杂的递归数据结构。 尝试将/menu/:optioId/select/:selectId 放入dashboard 命名空间:

const result = [
  route(`/dashboard/:siteId`, (arg) => { },
    route(`/dashboard/:siteId/friend/:friendId`, (arg) => { }),
    route(`/dashboard/:siteId/widgets/:widgetId`, (arg) => { },),
    route(`/menu/:optioId/select/:selectId`, (arg) => { }) // error
  ),
]

您将收到错误消息,因为 dashboard 命名空间需要带有 dashboard 的字符串。

【讨论】:

    猜你喜欢
    • 2014-11-11
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 1970-01-01
    • 2021-01-30
    • 1970-01-01
    相关资源
    最近更新 更多