You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为带动态属性名的TypeScript函数添加返回类型提示?

解决TypeScript动态属性重命名的类型提示问题

问题背景

我写了一个用来重命名react-query UseQueryResult 属性的TypeScript函数,能根据传入的名称前缀自动生成新属性,但使用时TypeScript无法识别这些动态生成的属性(比如解构cards时提示不存在),需要给函数添加正确的返回类型提示,同时避免手动逐个重命名的繁琐。

原函数代码:

function refactorQueryResult<D, E>(
  name: string,
  original: UseQueryResult<D, E>
) {
  return {
    [name]: original.data,
    [`${name}UpdatedAt`]: original.dataUpdatedAt,
    [`${name}Error`]: original.error,
    [`${name}ErrorUpdatedAt`]: original.errorUpdatedAt,
    [`${name}FailureCount`]: original.failureCount,
    [`${name}HasError`]: original.isError,
    [`${name}Fetched`]: original.isFetched,
    [`${name}FetchedAfterMount`]: original.isFetchedAfterMount,
    [`${name}Fetching`]: original.isFetching,
    [`${name}Idle`]: original.isIdle,
    [`${name}Loading`]: original.isLoading,
    [`${name}HasLoadingError`]: original.isLoadingError,
    [`${name}IsPlaceholder`]: original.isPlaceholderData,
    [`${name}IsPrevious`]: original.isPreviousData,
    [`${name}HasRefetchError`]: original.isRefetchError,
    [`${name}Refetching`]: original.isRefetching,
    [`${name}HasSuccess`]: original.isSuccess,
    [`${name}IsStale`]: original.isStale,
    [`fetch${name[0].toUpperCase()}${name.slice(1)}`]: original.refetch,
    [`remove${name[0].toUpperCase()}${name.slice(1)}`]: original.remove,
    [`${name}Status`]: original.status,
  };
}

使用时报错:

// TypeScript提示`cards`不存在,但实际属性存在
const { cards } = refactorQueryResult("cards", useQuery(listCards(filters)))

解决方案

核心思路是利用字符串字面量类型和映射类型让TypeScript跟踪动态属性的具体名称,步骤如下:

1. 定义泛型映射类型

先创建一个映射类型,把UseQueryResult的每个属性对应到带前缀的新属性:

import { UseQueryResult } from '@tanstack/react-query';

// 辅助类型:将首字母大写
type CapitalizeFirstLetter<S extends string> = S extends `${infer First}${infer Rest}` 
  ? `${Uppercase<First>}${Rest}` 
  : S;

// 核心映射类型:根据名称前缀生成新属性
type RefactoredQueryResult<N extends string, D, E> = {
  [K in keyof UseQueryResult<D, E> as K extends 'data' 
    ? N 
    : K extends 'dataUpdatedAt' 
      ? `${N}UpdatedAt` 
      : K extends 'error' 
        ? `${N}Error` 
        : K extends 'errorUpdatedAt' 
          ? `${N}ErrorUpdatedAt` 
          : K extends 'failureCount' 
            ? `${N}FailureCount` 
            : K extends 'isError' 
              ? `${N}HasError` 
              : K extends 'isFetched' 
                ? `${N}Fetched` 
                : K extends 'isFetchedAfterMount' 
                  ? `${N}FetchedAfterMount` 
                  : K extends 'isFetching' 
                    ? `${N}Fetching` 
                    : K extends 'isIdle' 
                      ? `${N}Idle` 
                      : K extends 'isLoading' 
                        ? `${N}Loading` 
                        : K extends 'isLoadingError' 
                          ? `${N}HasLoadingError` 
                          : K extends 'isPlaceholderData' 
                            ? `${N}IsPlaceholder` 
                            : K extends 'isPreviousData' 
                              ? `${N}IsPrevious` 
                              : K extends 'isRefetchError' 
                                ? `${N}HasRefetchError` 
                                : K extends 'isRefetching' 
                                  ? `${N}Refetching` 
                                  : K extends 'isSuccess' 
                                    ? `${N}HasSuccess` 
                                    : K extends 'isStale' 
                                      ? `${N}IsStale` 
                                      : K extends 'refetch' 
                                        ? `fetch${CapitalizeFirstLetter<N>}` 
                                        : K extends 'remove' 
                                          ? `remove${CapitalizeFirstLetter<N>}` 
                                          : K extends 'status' 
                                            ? `${N}Status` 
                                            : never
] : UseQueryResult<D, E>[K];

2. 修改函数的泛型参数

将name参数约束为字符串字面量类型N,并指定返回类型为上面定义的映射类型:

function refactorQueryResult<N extends string, D, E>(
  name: N,
  original: UseQueryResult<D, E>
): RefactoredQueryResult<N, D, E> {
  return {
    [name]: original.data,
    [`${name}UpdatedAt`]: original.dataUpdatedAt,
    [`${name}Error`]: original.error,
    [`${name}ErrorUpdatedAt`]: original.errorUpdatedAt,
    [`${name}FailureCount`]: original.failureCount,
    [`${name}HasError`]: original.isError,
    [`${name}Fetched`]: original.isFetched,
    [`${name}FetchedAfterMount`]: original.isFetchedAfterMount,
    [`${name}Fetching`]: original.isFetching,
    [`${name}Idle`]: original.isIdle,
    [`${name}Loading`]: original.isLoading,
    [`${name}HasLoadingError`]: original.isLoadingError,
    [`${name}IsPlaceholder`]: original.isPlaceholderData,
    [`${name}IsPrevious`]: original.isPreviousData,
    [`${name}HasRefetchError`]: original.isRefetchError,
    [`${name}Refetching`]: original.isRefetching,
    [`${name}HasSuccess`]: original.isSuccess,
    [`${name}IsStale`]: original.isStale,
    [`fetch${name[0].toUpperCase()}${name.slice(1)}`]: original.refetch,
    [`remove${name[0].toUpperCase()}${name.slice(1)}`]: original.remove,
    [`${name}Status`]: original.status,
  } as RefactoredQueryResult<N, D, E>;
}

3. 使用验证

现在调用函数时,TypeScript能正确推断所有动态生成的属性:

// 无报错,cards、cardsFetched、fetchCards等属性均能被正确识别
const { cards, cardsFetched, fetchCards } = refactorQueryResult("cards", useQuery(listCards(filters)))

关键说明

  • 之前的写法失败是因为name参数是string类型,TypeScript无法跟踪具体的字符串值;将其约束为字符串字面量类型N extends string后,TypeScript能基于这个字面量推导所有动态属性的名称。
  • 如果name是动态变量(不是字面量),需要显式指定泛型参数:
    const queryName = "cards";
    const result = refactorQueryResult<typeof queryName, Card[], Error>(queryName, useQuery(listCards(filters)));
    

内容的提问来源于stack exchange,提问作者kaan_atakan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 00:25:32