從元件中取得Props類型並省略其中的屬性
P粉704066087
P粉704066087 2024-01-16 12:55:57
0
1
559

我正在為 React「高階元件」開發 TypeScript 函數。需要:

  • 一個元件,
  • React Query 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 呼叫提供不需要由父元件傳入的道具。

例如,如果我這樣做:

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粉704066087
P粉704066087

全部回覆(1)
P粉203648742

如果我從您的問題中理解正確,您想要推斷傳遞到 withQuery 的元件類型,並從其 props 中刪除傳遞到 resultKey 參數的屬性。

您可以使用 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;
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板