简体   繁体   English

如何将 GraphQL 与 TypeScript 和从 graphql-code-generator 生成的类型一起使用?

[英]How to use GraphQL with TypeScript and typings generated from graphql-code-generator?

I'm following the Apollo Docs tutorial to build an Apollo Server (Express) with TypeScript and I'm also using GraphQL Code Generator to generate the necessary typings based on my GraphQL schema.我正在按照Apollo Docs 教程使用 TypeScript 构建 Apollo 服务器(Express),并且我还使用GraphQL 代码生成器根据我的 Z524DE3D2ADE4544176F6070ZB6 模式生成必要的类型。

This is my current codegen.json configuration:这是我当前的codegen.json配置:

{
  "schema": "./lib/schema/index.graphql",
  "generates": {
    "./dist/typings/graphql/schema.d.ts": {
      "plugins": [
        "typescript",
        "typescript-resolvers"
      ],
      "config": {
        "typesPrefix": "GQL",
        "skipTypename": true,
        "noSchemaStitching": true,
        "useIndexSignature": true
      }
    }
  }
}

This is my current GraphQL schema based on the tutorial (it's not complete, I haven't finished the whole thing yet and I've trimmed a few things to make the example smaller):这是我当前基于教程的 GraphQL 架构(它不完整,我还没有完成整个事情,我已经修剪了一些东西以使示例更小):

type Query {
    launch(id: ID!): Launch
}

type Launch {
    id: ID!
    site: String
    mission: Mission
}

enum PatchSize {
    SMALL
    LARGE
}

type Mission {
    name: String
    missionPatch(mission: String, size: PatchSize): String
}

Which generates the following TypeScript typings:生成以下 TypeScript 类型:

import { GraphQLResolveInfo } from 'graphql';
export type Maybe<T> = T | null;
export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string,
  String: string,
  Boolean: boolean,
  Int: number,
  Float: number,
};

export type GQLLaunch = {
  id: Scalars['ID'],
  site?: Maybe<Scalars['String']>,
  mission?: Maybe<GQLMission>,
};

export type GQLMission = {
  name?: Maybe<Scalars['String']>,
  missionPatch?: Maybe<Scalars['String']>,
};


export type GQLMissionMissionPatchArgs = {
  mission?: Maybe<Scalars['String']>,
  size?: Maybe<GQLPatchSize>
};

export enum GQLPatchSize {
  Small = 'SMALL',
  Large = 'LARGE'
}

export type GQLQuery = {
  launch?: Maybe<GQLLaunch>,
};


export type GQLQueryLaunchArgs = {
  id: Scalars['ID']
};

export type WithIndex<TObject> = TObject & Record<string, any>;
export type ResolversObject<TObject> = WithIndex<TObject>;

export type ResolverTypeWrapper<T> = Promise<T> | T;

export type ResolverFn<TResult, TParent, TContext, TArgs> = (
  parent: TParent,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => Promise<TResult> | TResult;

export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs>;

export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
  parent: TParent,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;

export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
  parent: TParent,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => TResult | Promise<TResult>;

export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
  subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
  resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
}

export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
  subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
  resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
}

export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
  | SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
  | SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;

export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
  | ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
  | SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;

export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
  parent: TParent,
  context: TContext,
  info: GraphQLResolveInfo
) => Maybe<TTypes>;

export type NextResolverFn<T> = () => Promise<T>;

export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
  next: NextResolverFn<TResult>,
  parent: TParent,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => TResult | Promise<TResult>;

/** Mapping between all available schema types and the resolvers types */
export type GQLResolversTypes = ResolversObject<{
  Query: ResolverTypeWrapper<{}>,
  ID: ResolverTypeWrapper<Scalars['ID']>,
  Launch: ResolverTypeWrapper<GQLLaunch>,
  String: ResolverTypeWrapper<Scalars['String']>,
  Mission: ResolverTypeWrapper<GQLMission>,
  PatchSize: GQLPatchSize,
  Boolean: ResolverTypeWrapper<Scalars['Boolean']>,
}>;

/** Mapping between all available schema types and the resolvers parents */
export type GQLResolversParentTypes = ResolversObject<{
  Query: {},
  ID: Scalars['ID'],
  Launch: GQLLaunch,
  String: Scalars['String'],
  Mission: GQLMission,
  PatchSize: GQLPatchSize,
  Boolean: Scalars['Boolean'],
}>;

export type GQLLaunchResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Launch'] = GQLResolversParentTypes['Launch']> = ResolversObject<{
  id?: Resolver<GQLResolversTypes['ID'], ParentType, ContextType>,
  site?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType>,
  mission?: Resolver<Maybe<GQLResolversTypes['Mission']>, ParentType, ContextType>,
}>;

export type GQLMissionResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Mission'] = GQLResolversParentTypes['Mission']> = ResolversObject<{
  name?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType>,
  missionPatch?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType, GQLMissionMissionPatchArgs>,
}>;

export type GQLQueryResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Query'] = GQLResolversParentTypes['Query']> = ResolversObject<{
  launch?: Resolver<Maybe<GQLResolversTypes['Launch']>, ParentType, ContextType, RequireFields<GQLQueryLaunchArgs, 'id'>>,
}>;

export type GQLResolvers<ContextType = any> = ResolversObject<{
  Launch?: GQLLaunchResolvers<ContextType>,
  Mission?: GQLMissionResolvers<ContextType>,
  Query?: GQLQueryResolvers<ContextType>,
}>;

This is my resolvers.ts file:这是我的resolvers.ts文件:

import { GQLPatchSize } from '@typings/graphql/schema';
import { GQLResolvers } from '@typings/graphql/schema';

const resolvers: GQLResolvers = {
    Query: {
        launch: (_, args, { dataSources }) => {
            return dataSources.launchesAPI.getLaunchById(args);
        },
    },
    Mission: {
        missionPatch: (mission, { size } = { size: GQLPatchSize.Large }) => {
            return size === 'SMALL' ? mission.missionPatchSmall : mission.missionPatchLarge;
        },
    },
};

export { resolvers };

And to finish, my launches.ts file with the LaunchesAPI class:最后,我的launches.ts文件带有LaunchesAPI class:

import { GQLLaunch } from '@typings/graphql/schema';
import { GQLQueryLaunchArgs } from '@typings/graphql/schema';
import { RESTDataSource } from 'apollo-datasource-rest';

const SPACEX_API_ENDPOINT = 'https://api.spacexdata.com/v3/';

class LaunchesAPI extends RESTDataSource {
    constructor() {
        super();

        this.baseURL = SPACEX_API_ENDPOINT;
    }

    async getLaunchById({ id }: GQLQueryLaunchArgs) {
        const response = await this.get('launches', { flight_number: id });
        return this.launchReducer(response[0]);
    }

    launchReducer(launch: any): GQLLaunch {
        return {
            id: String(launch.flight_number) || '0',
            site: launch.launch_site && launch.launch_site.site_name,
            mission: {
                name: launch.mission_name,
                missionPatchSmall: launch.links.mission_patch_small,
                missionPatchLarge: launch.links.mission_patch,
            },
        };
    }
}

export { LaunchesAPI };

Now, because I'm typing the result of launchReducer() with GQLLaunch , the mission property type is GQLMission and this type only has two properties, name and missionPatch .现在,因为我用GQLLaunch输入了launchReducer()的结果,所以mission属性类型是GQLMission并且这个类型只有两个属性, namemissionPatch It does not have missionPatchSmall or missionPatchLarge and thus I get this error:它没有missionPatchSmallmissionPatchLarge ,因此我收到此错误:

Type '{ name: any;键入'{名称:任何; missionPatchSmall: any;任务补丁小:任何; missionPatchLarge: any;任务补丁大:任意; }' is not assignable to type 'GQLMission'. }' 不可分配给类型 'GQLMission'。 Object literal may only specify known properties, and 'missionPatchSmall' does not exist in type 'GQLMission'. Object 文字只能指定已知属性,并且“GQLMission”类型中不存在“missionPatchSmall”。 ts(2339) ts(2339)

A similar error exists in the resolvers.ts file when it tries to read mission.missionPatchSmall or mission.missionPatchLarge as they don't exist in the mission object of type GQLMission :resolvers.ts文件尝试读取mission.missionPatchSmallmission.missionPatchLarge时存在类似错误,因为它们在 GQLMission 类型的mission GQLMission中不存在:

Property 'missionPatchSmall' does not exist on type 'GQLMission'. “GQLMission”类型上不存在属性“missionPatchSmall”。 ts(2339) ts(2339)

I'm not sure how to handle this, suggestions?我不确定如何处理这个,建议?

You're putting properties on mission that aren't a part of GQLMission and then explicitly typing mission to GQLMission .您正在将不属于GQLMission的属性放在mission上,然后将mission显式键入GQLMission Put generally, you are attempting to generate your types from your schema, but the return type from your resolver does not match what is specified by your schema.一般来说,您正试图从您的架构中生成您的类型,但您的解析器的返回类型与您的架构指定的类型不匹配。

Most of the time, the challenge you're facing is caused by either some deficiency in schema design or some hackery in resolver implementation.大多数时候,您面临的挑战是由架构设计中的一些缺陷或解析器实现中的一些黑客行为引起的。

As such, your options are generally:因此,您的选择通常是:

  • abandon using schema-generated types for your resolvers ( This is my least favorite option. )放弃为解析器使用模式生成的类型(这是我最不喜欢的选项。
  • change your schema to match your resolver return type ( Resolve schema deficiency. )更改您的架构以匹配您的解析器返回类型(解决架构缺陷。
  • change your resolver to match your schema return type ( Resolve resolver deficiency. )更改您的解析器以匹配您的架构返回类型(解决解析器缺陷。
  • change both your schema and resolver to return some new shared type ( Resolve schema deficiency and update resolver implementation for new schema. )更改您的架构和解析器以返回一些新的共享类型(解决架构缺陷并更新新架构的解析器实现。

Assuming you're intent on moving forward using schema-generated types for your resolvers, we can eliminate option 1 and consider the last three options as applied to your situation.假设您打算为解析器使用模式生成的类型继续前进,我们可以消除选项 1 并考虑适用于您的情况的最后三个选项。

  1. Yield to your resolver as implementing the correct type, and update your schema to match.让您的解析器实现正确的类型,并更新您的架构以匹配。 This would mean changing the GQLMission type in your schema to match the return type from your resolver (include both missionPatchLarge and missionPatchSmall properties) and allowing your clients to query one or both via their query of the schema directly.这意味着更改架构中的GQLMission类型以匹配来自解析器的返回类型(包括missionPatchLargemissionPatchSmall属性),并允许您的客户直接通过他们对架构的查询来查询一个或两个。
  2. Yield to your schema as the correct type, and update your resolver implementation to match.让您的模式作为正确的类型,并更新您的解析器实现以匹配。 This would mean getting rid of the excess returned properties ( missionPatchLarge and missionPatchSmall ), which you're currently using to ease implementation, and fetch the appropriate missionPatch value anew in the missionPatchResolver subresolver (ideally hitting cache to prevent perf hit).这将意味着摆脱您当前用于简化实现的多余返回属性( missionPatchLargemissionPatchSmall ),并在missionPatchResolver子解析器中重新获取适当的missionPatch值(理想情况下会命中缓存以防止性能命中)。
  3. Rethink your representation of missionPatch on your schema altogether.重新考虑您在架构上的missionPatch表示。 Consider the nature of a missionPatch .考虑一个missionPatch的性质。 Is it really an either/or situation?这真的是非此即彼的情况吗? This solution would involve changing the shape of the schema API around size and missionPatch , which would then need to be mirrored on your resolver implementation.此解决方案将涉及围绕 size 和missionPatch更改模式 API 的形状,然后需要在您的解析器实现上进行镜像。

What you do will depend on what the nature of a missionPatch is.您所做的将取决于missionPatch的性质。 My guess is that one of the last three options make sense here.我的猜测是最后三个选项之一在这里有意义。 If the two missionPatch types are actually different variants, it may make sense to change missionPatch to missionPatches , which returns an array of MissionPatch objects, which can be filtered by size .如果这两种missionPatch类型实际上是不同的变体,那么将missionPatch更改为missionPatches可能是有意义的,它返回一个MissionPatch对象数组,可以按size过滤。 If one is derivative of the other, it may make most sense to leave them as separate missionPatch and missionPatchSmall strings exposed through the schema.如果一个是另一个派生的,那么将它们作为单独missionPatchmissionPatchSmall字符串通过模式公开可能是最有意义的。

Edit: Looking into the api you're using, it's clear these are independent values that could both be requested.编辑:查看您正在使用的 api,很明显,这些都是可以请求的独立值。 There is no such thing as a small or large mission.没有小任务或大任务之类的东西。 These are images of different sizes for the same mission.这些是同一任务的不同尺寸的图像。 My approach would likely be to include both these values on your schema either directly or on a nested missionPatch property, eg我的方法可能是直接在您的架构中或在嵌套的missionPatch属性中包含这两个值,例如

export type GQLMission = {
  name?: Maybe<Scalars['String']>,

  smallPatchUrl: String,
  largePatchUrl: String,

  # OR

  patch?: MissionPatch,
};

export type MissionPatch = {
  smallUrl: String,
  largeUrl: String
};

Side-note: It isn't uncommon to represent images via their own value object type, which might include urls for different sizes of the image along with details about the image like aspect ratio or native width or height.旁注:通过它们自己的值 object 类型表示图像并不少见,它可能包括不同大小的图像的 url 以及有关图像的详细信息,如纵横比或原生宽度或高度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 Hasura graphql 模式时如何自定义 graphql-code-generator 生成字段的类型 - How to customize the type for a graphql-code-generator produced field when consuming a Hasura graphql schema 使用 graphql-code-generator 时找不到模块 - module not found when using graphql-code-generator 使用 graphql-code-generator 生成 React/Apollo 钩子时,数组类型是否不正确? - Are array types incorrect when generating React/Apollo hooks with graphql-code-generator? 覆盖 GraphQL Schema 生成的 Typescript 类型 - Override from GraphQL Schema generated Typescript types 如何在反应中使用 Graphql typescript 类型 - How to use Graphql typescript types in react 访问从 GraphQL 生成的多个嵌套 TypeScript 类型 - Access multiple nested TypeScript type generated from GraphQL 使用 GraphQL 代码生成器从解析器返回不完整的形状 - Return incomplete shapes from resolver with GraphQL Code Generator Laravel Lighthouse:如何让 graphql-codegen 从我的模式生成类型? - Laravel Lighthouse: How can I get graphql-codegen to generate typings from my schema? 打字稿:如何使用bowser打字? - Typescript: How to use bowser typings? 如何获取VS Code或Typescript以使用我的JSDoc类型 - How to get VS Code or Typescript to use my JSDoc typings
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM