Rufen Sie den Props-Typ aus der Komponente ab und lassen Sie die darin enthaltenen Eigenschaften weg
P粉704066087
P粉704066087 2024-01-16 12:55:57
0
1
582

Ich entwickle eine TypeScript-Funktion für eine React-Komponente „höherer Ordnung“. Erforderlich:

  • Eine Komponente,
  • Abfrage reagieren useQuery Typfunktion
  • Gibt das Parameterarray zurück und übergibt es an die obige useQueryTypfunktion
  • Optional resultKey, das bestimmt, ob Abfrageergebnisse in Komponenten weitergegeben oder unter einem bestimmten Schlüssel verschachtelt werden sollen.

Das ist meine bisherige Umsetzung:

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

Es funktioniert großartig, aber ich habe Probleme, den richtigen Typ zu finden. Im Idealfall würde ich eine Komponente (typisiert) übergeben und die Funktion würde aus dieser Komponente „ableiten“, welche endgültigen Requisiten die Komponente benötigt. Wenn Sie dann den withQuery 的结果将返回一个具有单独的、较小的所需道具集的组件,因为 withQuery-Aufruf für diese Komponente aufrufen, werden Requisiten bereitgestellt, die nicht von der übergeordneten Komponente übergeben werden müssen.

Zum Beispiel, wenn ich es tue:

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 Funktionen sollten idealerweise „smart“ genug sein:

  1. Leiten Sie „vollständige“ Requisitentypen (org und uuid) aus übergebenen Komponenten ab
  2. Verstehen Sie, dass, da „org“ resultKey,所以该 prop 是从查询传入的,不需要从外部传入。因此,可以从导出的组件类型中省略 Omit ist, die Requisite von der Abfrage übergeben wird und nicht von außen übergeben werden muss. Daher kann Omitted in exportierten Komponententypen weggelassen werden.

Super, super ideal, wenn useGetOrg eingegeben wird und kein resultKey übergeben wird (was bedeutet, dass das Ergebnis der Abfrage als Requisiten weitergegeben wird), useGetOrg ,并且没有传递 resultKey (意味着查询的结果作为 props 传播), withQuery die Funktion in der Lage ist, alle Schlüssel der Antwort zu erkennen werden von der Abfrage bereitgestellt und müssen daher nicht von der übergeordneten Rendering-Komponente übergeben werden.

Ist das möglich? Das übersteigt im Moment etwas meine TypeScript-Fähigkeiten.

Können Sie mir helfen, diese Methode zu überschreiben, um diese Typinferenz zu verarbeiten, sodass die übergeordnete Komponente nur Requisiten übergeben muss withQuery, die sie nicht selbst bereitstellt?

Oder, wenn das nicht möglich ist, könnten Sie beim Aufruf withQuery vielleicht den Requisitentyp der generierten Komponente übergeben?

P粉704066087
P粉704066087

Antworte allen(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;
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage