【问题标题】:Reflect and map typed argument shape in typescript?在打字稿中反映和映射类型化的参数形状?
【发布时间】:2019-06-01 00:10:56
【问题描述】:

上下文

我正在尝试构建一个代码生成工具。我喜欢使用 GraphQL,但是,当我拥有完整的堆栈时,我的前端使用字符串定义的 gql 查询调用我的后端似乎有点愚蠢。 GQL 是强类型的,因此我应该能够提供强类型的查询和响应。

问题

我不知道如何构建一个接口,以便我可以递归地反映并将参数从输入类型映射到目标类型。具体来说,我想将我的查询请求类型映射到一个 gql 查询响应类型。

const query: QueryRequest<Food.IFood> = {
  name: true // true ==> implies inclusion in response
}
const res = await client.food(query)
console.log(res.name) // PASS - should compile
console.log(res.nodeId) // FAIL - should not compile. `nodeId` was not present in query

// Food.IFood is a TS interface, representative of my GQL schema.  GQL => TS interfaces is a solved codegen problem already
// ref: https://github.com/cdaringe/gql-ts-client-codegen/blob/master/src/__tests__/fixture/namespace.ts
  • QueryRequest&lt;T&gt; 将我的 Food.IFood 接口(不完全)映射到一个新类型,其中键映射到布尔值,表示 GQL 字段包含
  • 但是,每个客户端方法都需要嗅探传递的QueryRequest&lt;T&gt; 以获得显式形状,并以某种方式将该显式形状映射到本质上的Partial&lt;Food.IFood&gt;
    • 很清楚,我想要Partial--Partial 对于存在哪些字段不明确。我希望客户端的响应具有明确的字段成员资格,作为输入的函数。

我了解,以上对我的 GQL 客户端的描述在很大程度上过于简化,并且放弃了符合所有 GQL 功能所需的其他复杂性。这很好。我在这篇文章中的主要目标是严格地看看是否有办法进行这种反射类型映射。

我已经开始草拟一个硬编码的目标 client.ts 文件,我希望潜在的输出如下所示:https://github.com/cdaringe/gql-ts-client-codegen/blob/master/src/target.ts

任何意见将不胜感激!谢谢。

【问题讨论】:

    标签: typescript codegen


    【解决方案1】:

    不幸的是,您想要的基本上是约束变量的类型,同时让编译器推断该变量的类型。不幸的是,这不可能直接。

    实现所需行为的唯一方法是使用函数。函数可以具有对其施加约束的泛型类型参数,但最终的类型参数将从传入的实际对象字面量中推断出来:

    type QueryRequest<T, K extends keyof T> = {
        keys: Record<K, boolean>
    } 
    
    function buildQueryRequest<T>() {
        return function <P extends keyof T> (o:Partial<Record<P, boolean>>) : QueryRequest<T, P>{
            return null!;
        }
    }
    
    
    interface IFood {
        name: string;
        nodeId: number;
    }
    
    
    type QueryResult<T, K extends keyof T> = Pick<T, K>
    declare class Client {
        food<K extends keyof IFood>(q: QueryRequest<IFood, K>) : Promise<QueryResult<IFood, K>>
    }
    
    (async function (client: Client) {
        const query = buildQueryRequest<IFood>()({
            name: true // true ==> implies inclusion in response
        })
        const res = await client.food(query)
        console.log(res.name) // PASS - should compile
        console.log(res.nodeId) // error
    })
    

    buildQueryRequest 是一个函数,它返回一个函数(即柯里化函数),以便指定第一个参数并推断第二个参数,

    【讨论】:

    • 嘿@titian,感谢您的反馈。受你的帖子启发,我做了一个更紧凑的尝试。我很接近,但是,在我的 impl 中,QueryResult 仍然没有完全有界——它仍然遭受失败,它认为来自 Food.IQuery 的所有键都可以在结果中使用。我有一个压缩到 ~40LOC 的例子。介意偷看吗? github.com/cdaringe/gql-ts-client-codegen/blob/…
    • @cdaringe 我现在正在旅行,这个评论可能会丢失......在 gitter 上写信给我,我明天会看看。 gitter 上的用户名也相同 :-)
    • @cdaringe 我认为你需要使用function exec &lt;P extends QueryRequest&lt;P&gt;&gt; (query: P)
    猜你喜欢
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-07
    • 2018-06-16
    • 2022-08-17
    • 1970-01-01
    • 2019-07-15
    相关资源
    最近更新 更多