Obtenez le type Props du composant et omettez les propriétés qu'il contient
P粉704066087
P粉704066087 2024-01-16 12:55:57
0
1
566

Je développe une fonction TypeScript pour un "composant d'ordre supérieur" React. Obligatoire :

  • Un composant,
  • Requête React useQuery Fonction de type
  • Renvoie le tableau de paramètres et le transmet à la fonction de type useQuery ci-dessus
  • Facultatif resultKey , qui détermine si les résultats de la requête doivent être propagés dans des composants ou imbriqués sous une clé donnée.

Voici ma mise en œuvre jusqu'à présent :

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

Cela fonctionne très bien, mais j'ai du mal à trouver le bon type. Idéalement, je transmettrais un composant (typé) et la fonction "déduirait" de ce composant quel est l'ensemble final d'accessoires dont le composant a besoin. Ensuite, appeler l'appel withQuery 的结果将返回一个具有单独的、较小的所需道具集的组件,因为 withQuery sur ce composant fournit des accessoires qui n'ont pas besoin d'être transmis par le composant parent.

Par exemple, si je le fais :

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 Les fonctions devraient idéalement être suffisamment "intelligentes" :

  1. Inférer les types d'accessoires "complets" (org et uuid) à partir des composants passés
  2. Comprenez que puisque « org » est resultKey,所以该 prop 是从查询传入的,不需要从外部传入。因此,可以从导出的组件类型中省略 Omit, l'accessoire est transmis à partir de la requête et n'a pas besoin d'être transmis de l'extérieur. Par conséquent, Omitted peut être omis des types de composants exportés.

Super, super idéal, si useGetOrg est entré et qu'aucun resultKey n'est passé (ce qui signifie que le résultat de la requête est propagé sous forme d'accessoires), useGetOrg ,并且没有传递 resultKey (意味着查询的结果作为 props 传播), withQuery la fonction sera capable de détecter que toutes les clés de la réponse sont fournis par la requête, donc pas besoin d'être transmis par le composant parent de rendu.

Est-ce possible ? C'est un peu au-delà de mes capacités TypeScript pour le moment.

Pouvez-vous m'aider à remplacer cette méthode pour gérer cette inférence de type afin que le composant parent n'ait qu'à transmettre les accessoires withQuery qu'il ne se fournit pas ?

Ou, si ce n'est pas possible, peut-être que lorsque vous appelez withQuery vous pourriez transmettre le type d'accessoires du composant généré ?

P粉704066087
P粉704066087

répondre à tous(1)
P粉203648742

Si je comprends bien votre question, vous souhaitez déduire les propriétés passées au paramètre withQuery 的组件类型,并从其 props 中删除传递到 resultKey.

Vous pouvez utiliser l'attribut React.ComponentProps 实用程序类型来提取组件的 props 类型。然后,您可以使用 Omit 类型实用程序从组件的 props 中提取传递到 resultKey du paramètre.

type ComponentProps = React.ComponentProps
type NeededProps = Omit

Voir cette réponse pour plus d'informations sur l'extraction des types de composants React Prop à partir du composant lui-même.

Alternativement, si vous souhaitez déduire le type de résultat de Query et supprimer les propriétés des accessoires en fonction de ce type de résultat, vous pouvez utiliser ResultType 实用程序类型和 keyof pour implémenter la fonctionnalité :

type KeysOfDataReturnType = keyof ReturnType['data'];
type NeededProps = Omit;
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal