我使用了出色的graphql-codgen/vue
,拥有完全类型安全的自动生成代码。
我在我的项目中通过构建一个包装器来使用它,这样我的用户就不必在每次调用时都执行常见的配置任务。像定义缓存行为,自动更新缓存,以正确的类型和格式分解结果。
使用JS和any
的包装器工作程序,但我也希望它是类型安全的,并且由于graphql-codegen
已经以类型安全的方式生成了所有类型和方法,因此我认为必须有一种方法做这个。我认为以某种方式区分工会...
因此归结为示例代码,我的问题是: 我有这个自动生成的代码:
//File GQLService.ts
export type CustodiansList = (
{ __typename: 'Query' }
& { custodiansList?: Maybe<Array<(
{ __typename: 'Custodian' }
& Pick<Custodian, 'id' | 'name' | 'street' | 'zip' | 'city' | 'telephone' | 'createdAt' | 'updatedAt'>
)>> }
);
type ReactiveFunctionCustodiansList = () => CustodiansListVariables
/**
* __useCustodiansList__
*
* To run a query within a Vue component, call `useCustodiansList` and pass it any options that fit your needs.
* When your component renders, `useCustodiansList` returns an object from Apollo Client that contains result, loading and error properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options;
*
* @example
* const { result, loading, error } = useCustodiansList(
* {
* }
* );
*/
export function useCustodiansList(variables?: CustodiansListVariables | VueCompositionApi.Ref<CustodiansListVariables> | ReactiveFunctionCustodiansList, baseOptions?: VueApolloComposable.UseQueryOptions<CustodiansList, CustodiansListVariables>) {
return VueApolloComposable.useQuery<CustodiansList, CustodiansListVariables>(CustodiansListDocument, variables, baseOptions);
}
export type CustodiansListCompositionFunctionResult = ReturnType<typeof useCustodiansList>;
现在我想像这样以最少的DRY“动态”使用它:
import * as Service from "./GQLService"; // from above
// e.g. typename = "custodian"
function useQueryList(typename:string) {
const fnName = toFunctionName(typename) // e.g. useCustodiansList
const result = Service[fnName](); //! this is the problem
// we also want to return everything including a parsedResult
const listName = `${typename}sList`
return {
[listName]: parseResult(result),
...result
}
}
我真的不想像必须回答其他问题那样,通过创建一个有区别的联合TypeTable
来重新创建graphql-codgen完成的所有工作,因为我认为所有工作已经完成了, graphql-codegen。
我的目标是使某个人可以创建一个新的ExamplesList.graphql
,graphql-codegen
进行包装,然后useQueryList("example")
即可使用它
因此,尽管这是一个动态传递的参数,但也必须有可能通过某种方式映射所有Service函数的返回类型,然后获取返回Array<__typename>
的返回静态类型来获得正确的静态类型。错误?而且我认为我必须通过解析typename
中所有可能的__typenames
来将Service
参数从字符串简化为字符串文字
const result = Service[fnName](); //! this is the problem
实际上并不是我们要做的所有事情,我们对其进行了包装和转换,但是一旦我在这里获得正确的类型,一切都会好起来的。
答案 0 :(得分:3)
我认为这个问题与TypeScript而不是与GraphQL Codegen有关。 基本上,您想做的是动态地从对象中获取函数属性,我不确定如果不向Codegen输出中添加某些内容,TypeScript就可以实现。
您可以创建一个自定义代码生成插件,该插件将根据您的所有查询生成一个对象,并带有您想要的单键(或者可能只是操作名称)。这样,您将能够获得"example"
和useExamplesListQuery
之间的映射。
答案 1 :(得分:3)
由于发现超级有趣,我对您的设置进行了一些尝试!
在这种情况下,您需要进行一些TypeScript取证:)在映射类型的帮助下,我可以将以下解决方案组合在一起。 我不知道您的解析函数的作用,因此我让它返回unknown
,但这应该很容易解决。
// Basic shape of a query result with __typename.
//
// I know your example only worked with lists,
// I added the singular form just in case :)
type QueryResultWithTypeName<T> = { __typename: T } | Array<{ __typename: T }>;
// A __typename (Custodian etc) based on a query result (CustodiansList etc)
type TypeNameForResult<R> = NonNullable<
{
[K in keyof R]: NonNullable<R[K]> extends QueryResultWithTypeName<infer T> ? T : never;
}[keyof R]
>;
// A result property name (custodiansList etc) based on a query result object (CustodiansList etc)
type PropertyNameForResult<R> = NonNullable<
{
[K in keyof R]: NonNullable<R[K]> extends QueryResultWithTypeName<string> ? K : never;
}[keyof R]
>;
// List of all available type names (Custodian etc)
type TypeName = {
[K in keyof ServiceType]: ServiceType[K] extends () => UseQueryReturn<infer TResult, any>
? TypeNameForResult<TResult>
: never;
}[keyof ServiceType];
// Map of type names (Custodian etc) and functions (useCustodianList etc)
//
// e.g. type UseCustodiansList = FunctionByTypeName['Custodian']
type FunctionByTypeName = {
[K in TypeName]: {
[L in keyof ServiceType]: ServiceType[L] extends () => UseQueryReturn<infer TResult, any>
? TypeNameForResult<TResult> extends K
? ServiceType[L]
: never
: never;
}[keyof ServiceType];
};
// Map of type names (Custodian) and property names (custodiansList etc)
//
// e.g. type CustodianProperty = PropertyNameByTypeName['Custodian'] // will be 'custodiansList'
type PropertyNameByTypeName = {
[K in keyof FunctionByTypeName]: FunctionByTypeName[K] extends () => UseQueryReturn<infer TResult, any>
? PropertyNameForResult<TResult>
: never;
};
// Map of type names (Custodian) and function return types
//
// e.g. type CustodianProperty = ReturnTypeByTypeName['Custodian'] // will be UseQueryReturn<CustodiansList, CustodiansListVariables>
type ReturnTypeByTypeName = {
[K in keyof FunctionByTypeName]: ReturnType<FunctionByTypeName[K]>;
};
// Type for the the return object from useQueryList
// (I was not sure what the result of your parsing is so I just used unknown)
//
// e.g. type UseCustodiansQueryReturnType = UseQueryListReturnType<'Custodian'> // will be { custodiansList: {}, /* the rest of UseQueryReturn */ }
type UseQueryListReturnType<T extends TypeName> = ReturnTypeByTypeName[T] &
{
[K in PropertyNameByTypeName[T]]: unknown;
// I would suggest though to not name the parsed result depending on the type name
// and make it consistent for all the types, e.g. call it parsedResult:
//
// parsedResult: unknown;
};
// A helper function to turn 'Custodian' into 'custodian' etc to get the property name from type name later
const lowercaseFirstLetter = (value: string) => (value ? value[0].toLowerCase() + value.slice(1) : value);
// This was undefined in your example
const parseResult = <T>(a: T): T => a;
// Convert typename to a function
const toFunction = <T extends TypeName>(typename: T): FunctionByTypeName[T] => {
// This is the first type casting you need to make since string manipulation and types don't go together
return Service[`use${typename}sList` as keyof ServiceType];
};
// Convert typename to property name (e.g. 'Custodian' => 'custodiansList')
const toPropertyName = <T extends TypeName>(typename: T): PropertyNameByTypeName[T] =>
// Again the same string manipulation problem
`${lowercaseFirstLetter(typename)}sList` as PropertyNameByTypeName[T];
function useQueryList<T extends TypeName>(typename: T): UseQueryListReturnType<T> {
const fn: FunctionByTypeName[T] = toFunction(typename); // e.g. useCustodiansList
const result: ReturnTypeByTypeName[T] = fn(); //! this is the problem
// we also want to return everything including a parsedResult
const listName: PropertyNameByTypeName[T] = toPropertyName(typename);
// Now the third type casting is something I am not proud of but unfortunately
// TypeScript does not want to agree with me that listName is not just a string
// but a very special string :)
return {
...result,
[listName]: parseResult(result),
} as UseQueryListReturnType<T>;
}
当我现在尝试:
const custodians = useQueryList('Custodian');
我可以看到usersList
属性在那里!是的!