typescript 属性与参数值同名返回类型

sg2wtvxw  于 2022-11-26  发布在  TypeScript
关注(0)|答案(2)|浏览(167)

我有这样一个函数:

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,
  };
}

但令我沮丧的是,当我使用结果时:

const { cards } = refactorQueryResult("cards", useQuery(listCards(filters)))

Typescript抱怨卡片不存在,但它当然存在。
有没有办法键入提示返回值?
记住我知道我可以做到:

const { data: cards, isFetched: cardsFetched /*, etc */ } = useQuery(listCards(filters))

但是我既懒惰又好奇,所以我想知道是否有一种方法可以浪费几个处理器周期,这样我就可以少打字了。
对于一个简单得多的函数,我尝试过:

function renameData<D, E>(
  name: string, 
  queryResult: UseQueryResult<D, E>
): { [name]: D } & UseQueryOptions<D, E> {
} & UseQueryResult<D, E> {
  return { [name]: queryResult.data, ...queryResult }
}

可是这里 typescript 埋怨道:A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type
我认为不添加另一个类型参数是不可能的。所以类似于:

export function refactorQueryResult<T, D, E>(
  name: string,
  original: UseQueryResult<D, E>
): ComplicatedTemplateMapping<T, UseQueryResult<D, E>> {
  // ...
}

// Then elsewhere
const { cards, cardsFetched } = refactorQueryResult<{ cards }>(
  "cards", useQuery(listCards(filters))
)

但在这里,我甚至不知道如何开始ComplicatedTemplateMapping中的检查和切片

mwg9r5ms

mwg9r5ms1#

请考虑Map方法:

interface UseQueryResult<TData = unknown, TError = unknown> {
  data: TData | undefined;
  dataUpdatedAt: number;
  error: TError | null;
  errorUpdatedAt: number;
  failureCount: number;
  isError: boolean;
}

function refactorQueryResult<D, E, T extends string>(
  name: T,
  original: UseQueryResult<D, E>
): TransformedData<UseQueryResult<D, E>, T> {
  return {
    [name]: original.data,
    [`${name}UpdatedAt`]: original.dataUpdatedAt,
    [`${name}Error`]: original.error,
    [`${name}ErrorUpdatedAt`]: original.errorUpdatedAt,
    [`${name}FailureCount`]: original.failureCount,
    [`${name}HasError`]: original.isError,
  };
}

type MappedData<N extends string> = {
  data: N,
  dataUpdatedAt: `${N}UpdatedAt`,
  error: `${N}Error`,
  errorUpdatedAt: `${N}ErrorUpdatedAt`,
  failureCount: `${N}FailureCount`
  isError: `${N}HasError`
}

type TransformedData<QR, N extends string> = {
  [P in keyof QR as P extends keyof MappedData<N> ? MappedData<N>[P] : never]: QR[P]
}

function test(queryResult: UseQueryResult) {
  const {
    test,
    testError,
    testUpdatedAt,
    testErrorUpdatedAt,
    testHasError,
  } = refactorQueryResult("test", queryResult)
}

Playground

f3temu5u

f3temu5u2#

所以最后我自己解决了这个问题。事实证明,这是可能的!Typescript很神奇。诀窍是使用第三个类型参数。但实际上你根本不需要指定它。Typescript会检测到它。因此,尽管原始函数签名是:

function refactorQueryResult<D, E>(
  name: string,
  original: UseQueryResult<D, E>
)

它现在是:

function refactorQueryResult<D, E, T extends string>(
  name: T,
  original: UseQueryResult<D, E>
): RefactoredQueryResult<UseQueryResult<D, E>, T>

Typescript很聪明,它可以检测到T应该等于作为参数传入的字符串文字,因此调用以下语句就足够了:

const { test, testFetched } = refactorQueryResult("test", queryResult);

如果你提供一个字符串变量而不是一个文字作为name参数,它将不会工作。它不会显示错误,但结果类型将是一个泛型记录。如果变量的类型是一个字符串文字,它将工作,如果变量类型是一个字符串文字的并集,那么你将获得变量的每个可能值的键和原始类型的每个键。
型别本身的定义如下:

type RefactoredQueryResult<QR, N extends string> = {
  [K in keyof QR as K extends `data${infer R}`
    ? `${N}${Capitalize<R>}`
    : K extends `error${string}`
    ? `${N}${Capitalize<K>}`
    : K extends `is${infer R}Error`
    ? `${N}Has${Capitalize<R>}Error`
    : K extends `isSuccess`
    ? `${N}HasSuccess`
    : K extends `is${infer R}Data`
    ? `${N}Is${R}`
    : K extends `isStale`
    ? `${N}IsStale`
    : K extends `is${infer R}`
    ? `${N}${R}`
    : K extends `refetch` | `remove`
    ? `${K}${Capitalize<N>}`
    : K extends `status`
    ? `${N}Status`
    : never]: QR[K];
};

我了解到infer几乎是slice的类型等价物,尽管它可以通过更显式的类型完全避免。似乎没有像联合类型那样对条件应用同样的限制。所以实际上我在生产中使用显式版本。但我在这里提供了带有infer子句的较短版本,以帮助具有不同用例的用户。
下面是这段代码的一个练习场,它包括一个UseQueryResult类型的模拟,以及一个解构重构类型参数的函数,作为一种测试。

相关问题