我正在为 React“高阶组件”开发 TypeScript 函数。需要:
useQuery
类型函数useQuery
类型函数resultKey
,它确定查询结果是否应传播到组件中或嵌套在给定键下。这是我迄今为止的实现:
import React, { ComponentProps, FC } from "react"; import { UseQueryResult } from "react-query"; import { useParams } from "react-router-dom"; import { ReactQueryLoader } from "Components/Shared/Elements/ReactQueryLoader"; import { useErrorToast } from "Utils/toasts"; import { useQueryParams } from "Utils/uris"; /** The useQuery function returning the query result */ type QueryFunc = (...args: unknown[]) => UseQueryResult; /** Function returning array of args to pass to the query. Func is fed an object with URL params and passed component props. */ type GetArgsFunc<Props> = (getArgsArgs: { params: Record<string, string>; props: Props; queryParams: Record<string, unknown>; }) => unknown[]; /** The string value to pass the result under to the child component. If undefined, result is spread */ type ResultKey = string | undefined; type QueryTriplet<Props = Record<string, unknown>> = [QueryFunc, GetArgsFunc<Props>, ResultKey]; type QueryResult = Record<string, unknown> | Record<string, Record<string, unknown>>; /** * Sort of the React Query version of React Redux's `connect`. This provides a neater interface for "wrapping" a component * with the API data it requires. Until that data resolves, a loading spinner is shown. If an error hits, a toast is shown. * Once it resolves, the data is passed to the underlying component. * * This "wrapper" is a bit more complex than the typical useQuery pattern, and is mostly better for cases where you want the "main" component * to receive the data unconditionally, so it can use it in a useEffect, etc. * * @param Component The Component to be rendered once the provided query has been resolved * @param useQuery The React Query hook to be resolved and passed to the Component * @param getArgs A function returning an ordered array of args to pass to the query func. * getArgs takes an object with URL `params` and passed `props` * @param resultKey The name of the prop to pass the query data to the Component as. * If not provided, the incoming data from the query will be spread into the Component's props. * * @example * * const OrgNameContent = ({ org }: { org: CompleteOrg }) => { * const { name } = org; * return <div>Org name: {name}</div> * } * * export const OrgName = withQuery( * OrgNameContent, * useGetOrg, * ({ params }) => [params.uuid], // useGetOrg takes a single uuid param. The uuid comes from the URL. * "org" // The OrgNameContent component expects an "org" prop, so we pass the data as that prop. * ); */ export function withQuery<QueryFetchedKeys extends string = "", Props = Record<string, unknown>>( Component: FC<Props>, useQuery: QueryFunc, getArgs: GetArgsFunc<Props>, resultKey: ResultKey = undefined ) { type NeededProps = Omit<Props, QueryFetchedKeys>; const ComponentWithQuery: FC = (props: NeededProps) => { const showErrorToast = useErrorToast(); const params = useParams(); const queryParams = useQueryParams(); const queryArgs = getArgs({ params, props, queryParams }); const query = useQuery(...queryArgs) as UseQueryResult<QueryResult>; return ( <ReactQueryLoader useQueryResult={query} handleError={showErrorToast}> {({ data }) => { const resultProps = (resultKey ? { [resultKey]: data } : data) as | QueryResult | Record<string, QueryResult> as Props; return <Component {...props} {...resultProps} />; }} </ReactQueryLoader> ); }; return ComponentWithQuery as FC<NeededProps>; }
它工作得很好,但我在获取正确的类型时遇到了困难。理想情况下,我会传入一个组件(已键入),并且该函数将从该组件“推断”该组件需要的最终一组道具是什么。然后,在该组件上调用 withQuery
的结果将返回一个具有单独的、较小的所需道具集的组件,因为 withQuery
调用提供不需要由父组件传入的道具。 p>
例如,如果我这样做:
type SomeComponentProps = { uuid: string, org: Org }; const SomeComponentBase: FC<SomeComponentProps> = ({ org }) => ( <span>{org.name}</span> ) // Would expect `uuid` as a prop, but not `org` export const SomeComponent = withQuery( SomeComponent, useGetOrg, // This query expects a uuid arg, and returns an org ({ props }) => [props.uuid], // Grab the passed uuid, and pass it in as the first and only arg to the useOrg function 'org' // Assert that the result of the query (an org), should be passed as a prop under the key "org" )
withQuery
函数理想情况下应该足够“智能”:
resultKey
,所以该 prop 是从查询传入的,不需要从外部传入。因此,可以从导出的组件类型中省略 Omit
ted。超级,超级理想,如果输入 useGetOrg
,并且没有传递 resultKey (意味着查询的结果作为 props 传播), withQuery
函数将能够检测到该响应的所有键由查询提供,因此不需要由渲染父组件传入。
这可能吗?目前这有点超出了我的 TypeScript 能力。
你能帮我重写这个方法来处理这种类型推断,这样父组件只需要传入 withQuery
本身不提供的 props 吗?
或者,如果这是不可能的,也许当你调用 withQuery
时,你可以传入生成组件的 props 类型?
P粉2036487422024-01-17 10:53:01
如果我从您的问题中理解正确,您想要推断传递到 withQuery
的组件类型,并从其 props 中删除传递到 resultKey
参数的属性。< /p>
您可以使用 React.ComponentProps
实用程序类型来提取组件的 props 类型。然后,您可以使用 Omit
类型实用程序从组件的 props 中提取传递到 resultKey
参数的属性。
type ComponentProps = React.ComponentPropstype NeededProps = Omit
请参阅此答案,了解有关从组件本身提取 React 组件 Prop 类型的更多信息。
或者,如果您想推断 Query 的结果类型并根据该结果类型从 props 中删除属性,您可以使用 ResultType
实用程序类型和 keyof
来实现功能:
type KeysOfDataReturnType = keyof ReturnType['data']; type NeededProps = Omit ;