[英]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
并且这个类型只有两个属性, name
和missionPatch
。 It does not have missionPatchSmall
or missionPatchLarge
and thus I get this error:它没有
missionPatchSmall
或missionPatchLarge
,因此我收到此错误:
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.missionPatchSmall
或mission.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:因此,您的选择通常是:
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 并考虑适用于您的情况的最后三个选项。
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
类型以匹配来自解析器的返回类型(包括missionPatchLarge
和missionPatchSmall
属性),并允许您的客户直接通过他们对架构的查询来查询一个或两个。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).missionPatchLarge
和missionPatchSmall
),并在missionPatchResolver
子解析器中重新获取适当的missionPatch
值(理想情况下会命中缓存以防止性能命中)。missionPatch
on your schema altogether.missionPatch
表示。 Consider the nature of a missionPatch
.missionPatch
的性质。 Is it really an either/or situation?missionPatch
, which would then need to be mirrored on your resolver implementation.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.如果一个是另一个派生的,那么将它们作为单独
missionPatch
和missionPatchSmall
字符串通过模式公开可能是最有意义的。
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.