首頁  >  問答  >  主體

從元件中取得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粉704066087300 天前530

全部回覆(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
  • 取消回覆