当前位置: 首页 > 知识库问答 >
问题:

javascript - 如何完成这个 TS 函数的声明?

吕鹏
2023-10-18

我想封装一个通用的 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'}

现在可以在 paramnever 时省略第二个参数,但是 param 不是 never 时省略第二个参数没有错误提示。我应该如何改进我的代码?

共有5个答案

田信然
2023-10-18

一个函数,提供多种不同的参数方案,这种写法叫做重载
在 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 对于重载的识别是参数优先,类型次之,这我就不知道该如何解决了。
戴霖
2023-10-18
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);  // 这会报错
阮疏珂
2023-10-18
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

穆文斌
2023-10-18

never的就不用传参,不是never就必须传,没传会提示报错?

代码


理解错了,重新更新一版


不过个人觉得这种我还是喜欢一个接口单独一个方法,每个方法上去单独声明入参和返回值

傅安宁
2023-10-18

拿走不谢

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,而不是个具体的对象,上面的写法还是不行? 有没有好的方式解决?