reactjs - Kræver et barn, der accepterer et ref-attribut i React + Typescript


Ved at bruge React + Typescript vil jeg gerne oprette en children-prop, der kun accepterer et enkelt barn, som forstår og accepterer en ref-attribut. Grundlæggende skal min børnetype acceptere:

  • en instans af en React-klassekomponent
  • et standard React HTML-element (f.eks. <button>, <div>)
  • en forwardRef indpakket funktionel komponent

Det bør ikke acceptere:

  • en almindelig funktionskomponent
  • et tekstnode, som en streng eller et tal

Mit mål med denne children-type er at injicere en ref i barnet ved at bruge React.cloneElement med en vis grad af sikkerhed for, at ref ikke bliver fuldstændigt ignoreret.

Jeg har prøvet

import React from "react";

type Props = {
    // EDIT: This is how I originally typed `children` to
    // get a decent compile time check that returns errors
    // if the children is a text node or multiple elements.
    // The same effect can be achieved with the `ReactElement` type.
    // children: React.FunctionComponentElement<React.RefAttributes<HTMLElement>>;
    children: React.ReactElement;
};

const MyComponent = (props: Props) => {
    const childRef = React.useRef(null);

    const child = React.Children.only(props.children);
    if (!React.isValidElement(child)) {
        return props.children;
    }
    return React.cloneElement(child, {ref: childRef});
};

Dette kompilerer, og MyComponent accepterer ikke tekstnoder som børn, men det accepterer funktionelle komponenter uden forwardRef uden klage.

// This fails with an error that a text node is not a valid child.
<MyComponent>hello</MyComponent>

// This compiles without issue, but should fail because MyText is not a `forwardRef` component.
const MyText = () => <span>hello</span>;
<MyComponent><MyText/></MyComponent>

Jeg har også prøvet nogle andre typer, men dette føles som det tætteste, jeg er kommet.

Er det muligt at lave et typekrav som dette med Typescript? Hvis ja, hvordan kan det gøres?

Svar #1

Nej.

Der er ingen måde at håndhæve, at en instans af en React-komponent bruger React.forwardRef(). Det skyldes, at React ikke bevarer typen af den oprindelige funktion eller klasse, som en komponentinstans blev oprettet med. En instans af en React-komponent i JSX vil altid være JSX.Element, React.FunctionComponentElement<...> eller React.CElement<...>.

Eksempel :

const MyForwardedRefComponent = React.forwardRef<HTMLDivElement>((props, ref) => {
  return <div ref={ref}>{props.children}</div>;
});

// JSX.Element
const myInstance = <MyForwardedRefComponent />;

// React.FunctionComponentElement<{}>
const myOtherInstance = React.createElement(MyForwardedRefComponent);

Du kan dog håndhæve, at en React-komponent kun accepterer et enkelt barn-element, og TypeScript vil advare dig i overensstemmelse hermed. Dette er omtrent så godt, som du kan håbe på i dit tilfælde.

Eksempel :

const SingleChildOnly: React.FC<{ children: React.ReactElement }> = ({
  children,
}) => {
  const ref = React.useRef(null);
  const child = React.Children.only(children);

  return React.cloneElement(child, { ref });
};

const thisIsOkay = (
  <SingleChildOnly>
    <div>hello</div>
  </SingleChildOnly>
);

// error
const thisIsNotOkay = (
  <SingleChildOnly>
    <div>hello</div>
    <div>hello there</div>
  </SingleChildOnly>
);

Svar #2

Jeg kunne heller ikke finde en TypeScript-løsning på dette.

Det, jeg ville gøre, er at oprette en Tooltip-komponent til at indhylle andre komponenter med.

Det bedste jeg kan komme på indtil videre, er at skrive SingleChildOnly-komponenten som en brugerdefineret hook, som vil returnere: en reference at sætte på barnet, eventuelle props at tilføje til barnet og det Element, du ønsker at rendere.

På denne måde kan du tvinge børnekomponenten til at kunne acceptere en ref.

Ulempen er, at du skal skrive meget mere kode, når du bruger SingleChildOnly-komponenten.

Jeg tænkte på at skabe TextWithTooltip ButtonWithTooltip basis komponenter, der kun indkapsler et simpelt HTML span eller knap.

Ved ikke, om det er relevant for dit brugstilfælde

Relaterede artikler