代码之家  ›  专栏  ›  技术社区  ›  TLadd

对象两个属性之间的通用约束

  •  0
  • TLadd  · 技术社区  · 3 年前

    我正在尝试改进前端路由配置对象的类型。我正试着把领带系好 path 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 包括上面的代码

    0 回复  |  直到 3 年前
        1
  •  2
  •   captain-yossarian from Ukraine    3 年前

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

    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

    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
      ),
    ]
    

    你会得到一个错误,因为 仪表板 命名空间要求字符串具有 仪表板 .