我想封装一个通用的 get
函数:
enum Api { allPage = 'https://api.example.com/allPage', page = 'https://api.example.com/page/:id', comment = 'https://api.example.com/page/:id/comment'}type PageComment = { content: string }type Page = { content: string }type TMap = { [K in Api]: { [Api.allPage]: { param: never query: { limit: number } data: Page[] }, [Api.page]: { param: { id: number } query: never data: Page }, [Api.comment]: { param: { id: number } query: { limit: number } data: PageComment } }[K]}
其中 param
是必选的 url 参数,query
是可选的查询参数,data
指定 api 的返回类型,我希望能够这样使用 get
函数:
// https://api.example.com/allPage// 返回 Page[]get(Api.allPage)// https://api.example.com/page/12345/comment// 返回 PageCommentget(Api.comment, { param: { id: 12345 } })// https://api.example.com/page/12345/comment?limit=20get(Api.comment, { param: { id: 12345 }, query: { limit: 20 } })// 错误:url 参数 id 为指定// 返回 neverget(Api.comment)
这是我现在写的:
function get<T extends Api>( api: T, { param, query }: { param?: TMap[T]["param"], query?: TMap[T]['query'] } = {}): TMap[T]["data"] { // TODO throw 'Unimplented'}
现在可以在 param
为 never
时省略第二个参数,但是 param
不是 never
时省略第二个参数没有错误提示。我应该如何改进我的代码?
一个函数,提供多种不同的参数方案,这种写法叫做重载。
在 TS 中,重载的方法是,把各种情形分开声明,然后再实现一个兼容所有重载的函数:
// allPage 属于特例,单独声明function get(api: Api.allPage): TMap[Api.allPage]["data"];// 非 appPage 的情形,另行声明function get<T extends Exclude<Api, Api.allPage>>( api: T, options: { param: TMap[T]["param"]; query: TMap[T]["query"]; }): TMap[T]["data"];// 下面是具体实现,同时兼容上面两种声明function get<T extends Api>( api: Api, options?: { param: TMap[T]["param"]; query: TMap[T]["query"]; }) { // XXXXXXXXXXX}// 实际调用的时候,分别选其中一种参数方案传参即可get(Api.allPage); // no problemget(Api.comment); // 报参数错误
不过这样写还是有缺陷:单参数Api.comment
的情况下,TS 会认为你应该传Api.allPage
,而不是让你补一个options
参数。
我猜 TS 对于重载的识别是参数优先,类型次之,这我就不知道该如何解决了。
enum Api { allPage = 'https://api.example.com/allPage', page = 'https://api.example.com/page/:id', comment = 'https://api.example.com/page/:id/comment'}type PageComment = { content: string }type Page = { content: string }type TMap = { [K in Api]: { [Api.allPage]: { param: never query: { limit: number } data: Page[] }, [Api.page]: { param: { id: number } query: never data: Page }, [Api.comment]: { param: { id: number } query: { limit: number } data: PageComment } }[K]}// 对于需要 param 的 APIfunction get<T extends Api>( api: T, options: TMap[T]['param'] extends never ? never : { param: TMap[T]['param'], query?: TMap[T]['query'] }): TMap[T]["data"];// 对于不需要 param 的 APIfunction get<T extends Api>( api: T, options?: TMap[T]['param'] extends never ? { query?: TMap[T]['query'] } : never): TMap[T]["data"];// 实现function get<T extends Api>( api: T, { param, query } = {} as { param?: TMap[T]["param"], query?: TMap[T]['query'] }): TMap[T]["data"] { // TODO throw 'Unimplented'}// 测试// 错误:参数“"https://api.example.com/page/:id/comment"”的类型与类型参数“T”不匹配。// "https://api.example.com/page/:id/comment" 的类型参数不可赋值给 "https://api.example.com/allPage" 的类型参数。// 类型 "https://api.example.com/page/:id/comment" 与字符串 "https://api.example.com/allPage" 不具有相同的属性。get(Api.comment); // 这会报错
enum Api { allPage = 'https://api.example.com/allPage', page = 'https://api.example.com/page/:id', comment = 'https://api.example.com/page/:id/comment'}type PageComment = { content: string }type Page = { content: string }type TMap = { [K in Api]: { [Api.allPage]: { // param: never query: { limit: number } data: Page[] }, [Api.page]: { param: { id: number } // query: never data: Page }, [Api.comment]: { param: { id: number } query: { limit: number } data: PageComment } }[K]}function get<T extends Api>( api: T, getParams:Omit< TMap[T],'data'>): TMap[T]["data"] { // TODO throw 'Unimplented'}// https://api.example.com/allPage// 返回 Page[]// https://api.example.com/allPage// 返回 Page[]get(Api.allPage)// https://api.example.com/page/12345/comment// 返回 PageCommentget(Api.comment, { param: { id: 12345 } })// https://api.example.com/page/12345/comment?limit=20get(Api.comment, { param: { id: 12345 }, query: { limit: 20 } })// 错误:url 参数 id 为指定// 返回 neverget(Api.comment)
不建议用 never
是never
的就不用传参,不是never
就必须传,没传会提示报错?
代码
理解错了,重新更新一版
不过个人觉得这种我还是喜欢一个接口单独一个方法,每个方法上去单独声明入参和返回值
拿走不谢
type Opts<T extends Api> = TMap[T]['param'] extends never ? [opts?: { query?: TMap[T]['query'] }] : [opts: { param: TMap[T]['param']; query?: TMap[T]['query'] }]function get<T extends Api>(api: T, ...args: Opts<T>): TMap[T]['data'] { // TODO throw 'Unimplented'}
也可以使用函数重载,一个对外强制类型,一个对内稍微宽松一些
function get<T extends Api>(api: T, ...args: Opts<T>): TMap[T]['data'];function get(api: Api, opts: {param?: object, query?: object} = {}): any { // TODO throw 'Unimplented'}
这是我的actionCreator代码,crud所有的代码都一样,只需要改一个 “getMovies" 这个名字,所以我想要把这个函数封装一下,每次只需要传入一个函数名字就行了。求大佬给封装一下?? 感谢
问题内容: 我正在一个有几个无法更改的脚本的项目中。这些脚本通过AJAX更新页面。更新完成后,我需要运行一些代码。 XMLHttpRequest完成时是否会触发任何事件?(或任何XMLHttpRequest状态更改?)。 不幸的是,我无法访问用于发出请求的特定XMLHttpRequest对象。 谢谢, 问题答案: 如果没有jQuery,则可以挂钩该方法以在XHR对象被编辑时为每个XHR对象的事件附
用户: 而此错误显示为:
我有这个,它将某个类解析为另一个类: 我的意思是,我可以使用类似这样的东西来完成这件事吗?
你能帮忙找出哪一部分错了吗,多谢。:)
react用vite搭建的项目 想要做代码提示,必须声明variables的类型,不想再像下面这样写一遍。 想直接根据scss中定义的变量生成类型声明,要怎么做?类似 但是因为variables是个cssmodule,而不是个具体的对象,上面的写法还是不行? 有没有好的方式解决?