使用代码分割时如何生成RTK-Query端点的正确Typescript类型
动态注入端点的TypeScript类型与预取钩子实现方案
方案1:全局类型扩充实现统一预取钩子
这个方案适合需要全局统一的usePrefetch钩子,支持所有已注入端点的场景:
- 首先在空API定义文件中预留可扩充的端点类型接口:
// emptySplitApi.ts import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const emptySplitApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: () => ({}), }) // 声明可扩充的端点名称接口 export interface SplitApiEndpointNames {} export type AllEndpointNames = SplitApiEndpointNames[keyof SplitApiEndpointNames]
- 每个调用
injectEndpoints的模块同步扩充该接口:
// userApi.ts import { emptySplitApi, type SplitApiEndpointNames } from './emptySplitApi' const extendedApi = emptySplitApi.injectEndpoints({ endpoints: (build) => ({ getUserInfo: build.query<User, number>({ query: (id) => `user/${id}` }), listUserOrders: build.query<Order[], number>({ query: (userId) => `user/${userId}/orders` }), }), }) // 扩充全局端点类型 declare module './emptySplitApi' { interface SplitApiEndpointNames { getUserInfo: 'getUserInfo' listUserOrders: 'listUserOrders' } } export const { useGetUserInfoQuery, useListUserOrdersQuery } = extendedApi
- 按照官方示例实现带类型的预取钩子即可:
import { useDispatch } from 'react-redux' import { useCallback } from 'react' import { emptySplitApi, type AllEndpointNames } from './emptySplitApi' import type { PrefetchOptions } from '@reduxjs/toolkit/query' export const usePrefetch = <T extends AllEndpointNames>(endpointName: T, options?: PrefetchOptions) => { const dispatch = useDispatch() return useCallback( (arg: Parameters<typeof emptySplitApi.endpoints[T]['initiate']>[0], prefetchOptions?: PrefetchOptions) => { dispatch(emptySplitApi.util.prefetch(endpointName, arg, { ...options, ...prefetchOptions })) }, [dispatch, endpointName, options] ) }
这个方案的usePrefetch会自动识别所有已注入的端点名称,以及对应端点的参数类型。
方案2:分模块导出独立预取钩子
这个方案适合完全隔离的代码分割场景,不需要全局类型扩充,每个模块的预取钩子仅支持当前模块注入的端点:
每个injectEndpoints的模块直接基于扩展后的API实例生成专属预取钩子:
// userApi.ts import { emptySplitApi } from './emptySplitApi' import { useDispatch } from 'react-redux' import { useCallback } from 'react' import type { PrefetchOptions } from '@reduxjs/toolkit/query' const userApi = emptySplitApi.injectEndpoints({ endpoints: (build) => ({ getUserInfo: build.query<User, number>({ query: (id) => `user/${id}` }), listUserOrders: build.query<Order[], number>({ query: (userId) => `user/${userId}/orders` }), }), }) // 生成仅支持当前模块端点的预取钩子 type UserEndpointNames = keyof typeof userApi.endpoints export const useUserPrefetch = <T extends UserEndpointNames>(endpointName: T, options?: PrefetchOptions) => { const dispatch = useDispatch() return useCallback( (arg: Parameters<typeof userApi.endpoints[T]['initiate']>[0], prefetchOptions?: PrefetchOptions) => { dispatch(userApi.util.prefetch(endpointName, arg, { ...options, ...prefetchOptions })) }, [dispatch, endpointName, options] ) } export const { useGetUserInfoQuery, useListUserOrdersQuery } = userApi
这个方案的优势是完全不需要维护全局类型,模块之间互不干扰,也不会出现调用了尚未注入的端点的类型提示。
注意事项
- 采用方案1时要保证类型扩充的模块路径完全匹配,否则类型不会正常合并
- 懒加载端点的场景优先选择方案2,类型安全性更高
内容的提问来源于stack exchange,提问作者Jake Ols
相关产品推荐
相关产品推荐

