typescript - 定义 1...n 个字符串模板类型的通用交集类型
问题描述
我正在尝试利用字符串模板文字类型的力量来为用于定义路由的字符串添加类型安全性。
- 例如,没有参数的路由可以是 any
string
。 - 具有单个参数的路由必须包含
:parameterName
在路径字符串中并parameterName
作为键/值包含在params
对象中。
我可以手动设置这些类型,并且效果很好。但我想做的是找到一种方法来消除开发人员手动链接交叉点的需要。我想在我的图书馆处理它。
type DynamicParam<S extends string> = `:${S}`
type DynamicParamRoute<T extends string> = `${string}/${DynamicParam<T>}/${string}` | `${DynamicParam<T>}/${string}` | `${string}/${DynamicParam<T>}` | `${DynamicParam<T>}`
type UserParamRoute = DynamicParamRoute<'user'>
// const bad1: UserParamRoute = 'user' // error as doesn't match ":user"
const u1: UserParamRoute = ':user'
const u2: UserParamRoute = 'prefix/:user'
const u3: UserParamRoute = ':user/suffix'
const u4: UserParamRoute = 'prefix/:user/suffix'
type TeamParamRoute = DynamicParamRoute<'team'>
const t1: TeamParamRoute = ':team'
const t2: TeamParamRoute = 'prefix/:team'
const t3: TeamParamRoute = ':team/suffix'
const t4: TeamParamRoute = 'prefix/:team/suffix'
// Combining them to get safety on a multi-param route string
type UserTeamParamRoute = UserParamRoute & TeamParamRoute // ***** I don't want my library consumer to be responsible for this. ******
// const ut1: UserTeamParamRoute = 'user/team' // Type '"user/team"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
// const ut1: UserTeamParamRoute = ':user' // Type '":user"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
// const ut1: UserTeamParamRoute = ':team' // Type '":team"' is not assignable to type 'UserTeamParamRoute'.ts(2322)
const ut1: UserTeamParamRoute = ':user/:team'
const ut2: UserTeamParamRoute = 'prefix/:user/:team'
const ut3: UserTeamParamRoute = ':user/:team/suffix'
const ut4: UserTeamParamRoute = ':user/middle/params/:team'
const tu1: UserTeamParamRoute = ':team/:user'
const tu2: UserTeamParamRoute = 'prefix/:team/:user'
const tu3: UserTeamParamRoute = ':team/:user/suffix'
const tu4: UserTeamParamRoute = ':team/middle/params/:user'
我尝试了一种将 keyof 用于params
对象的方法,但它创建了键的联合。路由定义需要params 对象,尤其是在有动态参数的情况下。path
因此,使用它的键似乎几乎是一种完美的方法来生成路由所需的类型。
const params = {
user: 'someting',
team: 'someotherthing'
} as const
const ps = ['user', 'team'] as const
type Params = keyof typeof params
// Doesn't work, no intersection to force requiring all the keys. Just union of everything
type RouteParams = DynamicParamRoute<Params> // type RouteParams = ":user" | `${string}/:user/${string}` | `:user/${string}` | `${string}/:user` | ":team" | `${string}/:team/${string}` | `:team/${string}` | `${string}/:team`
const r1: RouteParams = ':user' // means this is valid
const p1: RouteParams = ':team' // so is this
// const rp: RouteParams = 'userteam'
// const rp0: RouteParams = ''
// const rp01: RouteParams = 'missingall'
const rp1: RouteParams = ':user/:team'
const rp2: RouteParams = 'prefix/:user/:team'
const rp3: RouteParams = ':user/:team/suffix'
const rp4: RouteParams = ':user/middle/params/:team'
const pr1: RouteParams = ':team/:user'
const pr2: RouteParams = 'prefix/:team/:user'
const pr3: RouteParams = ':team/:user/suffix'
const pr4: RouteParams = ':team/middle/params/:user'
提前感谢您的任何见解!
更新:jcalz 解决方案演示为 GIF(我希望它不会太压缩)
[
解决方案
可以通过某种类型的杂耍将联合转换为 TypeScript 中的交集,从而将所讨论的联合置于逆变位置。这是我重新定义的方式DynamicParamRoute<T>
,以便联合在T
输出中变成交叉点:
type DynamicParamRoute<T extends string> = (T extends any ? (x:
`${string}/${DynamicParam<T>}/${string}` |
`${DynamicParam<T>}/${string}` |
`${string}/${DynamicParam<T>}` |
`${DynamicParam<T>}`
) => void : never) extends ((x: infer I) => void) ? I : never;
我已经用(T extends any ? (x: OrigDefinition<T>) => void : never) extends ((x: infer I) => void) ? I : never;
. 这将联合T
并分配它们,以便OrigDefinition<T>
分别应用于每个部分,并将它们作为函数参数(它们是逆变的),然后再从它们推断交集。它有点毛茸茸,但最终它会产生你想要的类型:
type RouteParams = DynamicParamRoute<Params>
/* (":user" | `${string}/:user/${string}` | `:user/${string}` | `${string}/:user`) &
(":team" | `${string}/:team/${string}` | `:team/${string}` | `${string}/:team`) */
这会导致您想要的错误:
const r1: RouteParams = ':user' // error!
const p1: RouteParams = ':team' // error!
我不确定模式模板文字类型的联合交集在实践中的扩展性如何,但这无论如何都超出了问题的范围。
推荐阅读
- javascript - 使用与搜索查询相同的路径更新 MongoDB 嵌套数组
- java - Android 9 无法在 Android 外部公共路径中创建目录(“storage/emulated/0/MyImages”)
- c# - 守护程序应用程序和范围
- java - 如何检测 Scheduled SpringBoot 任务是否已死锁?
- python - 如何返回列表中出现元素的数量?
- python - python 3中for循环的工作有增量问题
- microsoft-graph-api - 使用 Microsoft Graph API 创建新邮件
- ios - 如何检查 WatchKit 应用程序是否已关闭或终止?
- excel - 我希望我的代码将工作表选项卡传输到我只知道部分名称的 excel 文件
- azure - 如何使用 Azure APIM 将 API 请求中的参数传递到后端 URL?