@reduxjs/toolkit如何生成强类型动态API钩子?
以下是使用@reduxjs/toolkit创建API的简单示例:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; const output = createApi( { reducerPath: 'my-api', keepUnusedDataFor: CACHE_TIMEOUT, baseQuery: fetchBaseQuery( { baseUrl, fetchFn, prepareHeaders, } ), endpoints: ( builder ) => ( { foo: builder.query<any, Params>( { query: ( params: Params ) => { return {...}; }, } ), edit: builder.mutation<any, Params>( { query: ( params: Params ) => { return {...}; } } ), } ), } );
在这个示例中,output变量除了其他方法和属性外,还会基于endpoints自动生成3个强类型钩子:
output.useFooQueryoutput.useLazyFooQueryoutput.useEditMutation
我不清楚它底层是如何生成这些最终输出类型的,希望有人能解释原理并提供演示。我多次尝试阅读源码但进展有限,还没完全放弃,但想直接了解工作原理来节省时间——因为之后我想运用这项技术。感谢帮助。
核心原理:TypeScript 高级类型系统的编译期推导
RTK Query的类型生成完全依赖TypeScript的泛型、条件类型、映射类型组合,没有运行时代码生成,所有逻辑都在编译阶段完成,核心步骤如下:
1. 端点类型的基础绑定
builder.query和builder.mutation是泛型函数,会接收你传入的「返回值类型」和「参数类型」,生成带类型标记的端点定义:
builder.query<ResultType, ArgType>:标记该端点为查询类型,绑定返回值ResultType和入参ArgTypebuilder.mutation<ResultType, ArgType>:标记该端点为修改类型,同样绑定对应类型
这些函数会把你的端点配置(比如query函数)和泛型参数结合,生成包含完整类型信息的端点描述对象。
2. 端点类型的统一收集
createApi的泛型会自动解析endpoints回调返回的所有端点,整理成一个「端点类型映射表」。比如示例中的两个端点会被转化为:
type EndpointMap = { foo: QueryEndpointDefinition<any, Params>; edit: MutationEndpointDefinition<any, Params>; };
3. 钩子类型的自动生成
RTK Query内部预定义了一套类型工具,会遍历上述映射表,根据端点类型(query/mutation)生成对应钩子:
- 针对
query类型端点:- 生成
useXxxQuery:自动触发查询的钩子,类型继承端点的参数和返回值类型 - 生成
useLazyXxxQuery:手动触发查询的钩子,类型规则一致
- 生成
- 针对
mutation类型端点:- 生成
useXxxMutation:执行修改操作的钩子,绑定对应参数和返回值类型
- 生成
整个过程都是TypeScript的编译期类型推导,没有额外代码生成,最终output的类型会自动包含这些钩子。
简化演示:手动模拟类型生成逻辑
我们可以用TypeScript实现一个极简版的类型生成逻辑,直观理解这个过程:
// 定义基础端点类型 type QueryEndpoint<Result, Arg> = { type: 'query'; result: Result; arg: Arg }; type MutationEndpoint<Result, Arg> = { type: 'mutation'; result: Result; arg: Arg }; // 模拟builder的query/mutation方法 const mockBuilder = { query: <R, A>() => ({ type: 'query' } as QueryEndpoint<R, A>), mutation: <R, A>() => ({ type: 'mutation' } as MutationEndpoint<R, A>), }; // 核心类型工具:生成钩子类型 type GenerateHooks<Endpoints> = { // 遍历端点,根据类型生成钩子名称 [K in keyof Endpoints as Endpoints[K] extends QueryEndpoint<infer R, infer A> ? `use${Capitalize<string & K>}Query` | `useLazy${Capitalize<string & K>}Query` : Endpoints[K] extends MutationEndpoint<infer R, infer A> ? `use${Capitalize<string & K>}Mutation` : never ]: // 为钩子绑定对应参数和返回值类型 Endpoints[K] extends QueryEndpoint<infer R, infer A> ? (arg: A) => { data: R | undefined } : Endpoints[K] extends MutationEndpoint<infer R, infer A> ? () => [(arg: A) => Promise<R>, { data: R | undefined }] : never }; // 模拟createApi的返回类型 type MockApiReturn<Endpoints> = GenerateHooks<Endpoints> & { reducerPath: string; }; // 测试示例 type MyEndpoints = { foo: QueryEndpoint<any, Params>; edit: MutationEndpoint<any, Params>; }; type MyApi = MockApiReturn<MyEndpoints>; // 此时MyApi的类型会包含: // useFooQuery: (arg: Params) => { data: any | undefined } // useLazyFooQuery: (arg: Params) => { data: any | undefined } // useEditMutation: () => [(arg: Params) => Promise<any>, { data: any | undefined }]
这个示例模拟了RTK Query的核心逻辑——通过条件类型判断端点类型,映射类型生成钩子名称,类型推断绑定钩子的参数和返回值。
内容的提问来源于stack exchange,提问作者chris.middleton

