javascript - Udled React-barnets props i TypeScript-interface


Er det muligt at udlede props for det første børneelement og håndhæve dem i TypeScript? Jeg kan komme tæt på, men det begynder at fejle med generics, og jeg har ikke været i stand til at udlede typen.

Jeg prøver at videregive komponentprops fra en wrapper til det første børnekomponent med typesikkerhed. Når proppen ikke eksisterer på objektet, skal TS fejle, ellers passere.

import React, {
  Children,
  isValidElement,
  cloneElement,
  ReactNode,
} from 'react';

interface WrapperProps<C> {
  children: ReactNode;
  // how can I make typeof Button generic/inferred from the code?
  // firstChildProps: Partial<React.ComponentProps<typeof Button>>; // works with <C> removed
  firstChildProps: Partial<React.ComponentPropsWithoutRef<C>>;
}

const Wrapper: React.FC<WrapperProps<typeof Button>> = ({
  children,
  firstChildProps,
}) => {
  const firstChild = Children.toArray(children)[0];
  if (isValidElement(firstChild)) {
    // Clone the first child element and pass the firstChildProps to it
    return cloneElement(firstChild, { ...firstChildProps });
  } else {
    return <>{children}</>;
  }
};

interface ButtonProps {
  disabled: boolean;
  children: ReactNode;
}

const Button: React.FC<ButtonProps> = ({ disabled, children }) => {
  return <button disabled={disabled}>{children}</button>;
};

const Example = () => {
  return (
    <>
      {/* Passes type check because disabled exists on Button */}
      <Wrapper firstChildProps={{ disabled: false }}>
        <Button disabled={true}>Ok</Button>
      </Wrapper>
      {/* Fails type check because cheese does not exist on Button */}
      <Wrapper firstChildProps={{ cheese: true }}>
        <Button disabled={true}>Ok</Button>
      </Wrapper>
    </>
  );
};

Her er en næsten fungerende TS Playground.

Svar #1

  1. Du skal begrænse den generiske typeparameter C, så den passer til ComponentProps hjælpefunktionen, f.eks. med ElementType, som foreslået i SlavaSobolev's svar:
interface WrapperProps<C extends React.ElementType> {
    children: ReactNode;
    firstChildProps: Partial<React.ComponentPropsWithoutRef<C>>;
}
  1. Du kan også gøre Wrapper-komponenten generisk, men du ville være nødt til at droppe React.FC, som ikke fungerer med yderligere generics:
const Wrapper = <C extends React.ElementType>({ children, firstChildProps }: WrapperProps<C>) => {
  // etc.
}

Så kan du bruge det til forskelligt indhold:

interface OtherProps {
    foo: "bar";
}

const Other: React.FC<OtherProps> = () => null;

{/* Fails type check because cheese does not exist on Button */}
<Wrapper<typeof Button> firstChildProps={{ cheese: true }}>
    <Button disabled={true}>Ok</Button>
</Wrapper>

{/* Error: Type 'number' is not assignable to type '"bar"'. */}
<Wrapper<typeof Other> firstChildProps={{ foo: 0 }}>
    <Other foo="bar" />
</Wrapper>

Legeplads link

Svar #2

Du skal bare tilføje en generisk begrænsning til WrapperProps<C> - den samme, som ComponentPropsWithoutRef<C> har - nemlig ElementType.

interface WrapperProps<C extends React.ElementType> {
  children: ReactNode;
  firstChildProps: Partial<React.ComponentPropsWithoutRef<C>>;
}

Relaterede artikler