首页  >  问答  >  正文

从组件中获取Props类型并省略其中的属性

我正在为 React“高阶组件”开发 TypeScript 函数。需要:

这是我迄今为止的实现:

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 调用提供不需要由父组件传入的道具。

例如,如果我这样做:

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 函数理想情况下应该足够“智能”:

  1. 从传递的组件中推断“完整”的 prop 类型(org 和 uuid)
  2. 了解,因为“org”是 resultKey,所以该 prop 是从查询传入的,不需要从外部传入。因此,可以从导出的组件类型中省略 Omitted。

超级,超级理想,如果输入 useGetOrg ,并且没有传递 resultKey (意味着查询的结果作为 props 传播), withQuery 函数将能够检测到该响应的所有键由查询提供,因此不需要由渲染父组件传入。

这可能吗?目前这有点超出了我的 TypeScript 能力。

你能帮我重写这个方法来处理这种类型推断,这样父组件只需要传入 withQuery 本身不提供的 props 吗?

或者,如果这是不可能的,也许当你调用 withQuery 时,你可以传入生成组件的 props 类型?

P粉704066087P粉704066087249 天前478

全部回复(1)我来回复

  • P粉203648742

    P粉2036487422024-01-17 10:53:01

    如果我从您的问题中理解正确,您想要推断传递到 withQuery 的组件类型,并从其 props 中删除传递到 resultKey 参数的属性。< /p>

    您可以使用 React.ComponentProps 实用程序类型来提取组件的 props 类型。然后,您可以使用 Omit 类型实用程序从组件的 props 中提取传递到 resultKey 参数的属性。

    type ComponentProps = React.ComponentProps
    type NeededProps = Omit

    请参阅此答案,了解有关从组件本身提取 React 组件 Prop 类型的更多信息。

    或者,如果您想推断 Query 的结果类型并根据该结果类型从 props 中删除属性,您可以使用 ResultType 实用程序类型和 keyof 来实现功能:

    type KeysOfDataReturnType = keyof ReturnType['data'];
    type NeededProps = Omit;

    回复
    0
  • 取消回复