Weiterleiten von Refs

Weiterleiten von Refs ist eine Technik für die automatische Übergabe einer Ref durch eine Komponente an eines seiner Kinder. Normalerweise besteht für die meisten Komponenten innerhalb einer Anwendung kein Bedarf dafür. Nichtsdestotrotz, kann es für gewisse Arten von Komponenten nützlich sein, vor allem wenn es sich dabei um wiederverwendbare Komponenten-Bibliotheken handelt. Die gängigsten Szenarien werden unterhalb beschrieben.

Weiterleiten von Refs zu DOM-Komponenten

Stelle dir eine FancyButton Komponente vor, welche das native button DOM-Element rendert:

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

React Komponenten verbergen ihre Implementierungsdetails, einschließlich der gerenderten Ausgabe. Andere Komponenten die FancyButton benutzen, werden in den meisten Fällen keine Notwendigkeit für den Abruf einer Ref zum inneren button DOM-Element haben. Das ist gut so, da dies eine zu starke Abhängigkeit unter den Komponenten auf die gegenseitige DOM-Struktur verhindert.

Trotz der Tatsache, dass solch eine Kapselung für Komponenten in der Anwendungsebene wie FeedStory oder Comment erwünscht ist, kann dies für “Blatt-Komponenten” mit einem hohen Wiederverwendbarkeitsgrad, wie FancyButton oder MyTextInput unpraktisch sein. Diese Komponenten werden oft in der ganzen Anwendung als reguläre DOM button und input auf ähnliche Weise eingesetzt und der Zugriff auf dessen DOM-Knoten könnte für die Regelung von Fokus, Auswahl oder Animationen unvermeidlich sein.

Die Weiterleitung von Refs ist ein Opt-In Feature, welches manchen Komponenten die Möglichkeit bereitstellt, eine erhaltene ref, weiter nach unten zu einem Kind durchzulassen (in anderen Worten, “weiterzuleiten”).

Im unteren Beispiel benutzt FancyButton React.forwardRef, um die übermittelte ref abzurufen und diese anschließend an den gerenderten DOM button weiterzuleiten.

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// Du kannst jetzt ein Ref direkt zum DOM button bekommen:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

In diesem Fall, können die Komponenten die FancyButton nutzen, das Ref zum unterliegenden button DOM-Knoten abrufen und dies bei bedarf nutzen—so als ob sie direkt auf den DOM button zugreifen würden.

Hier ist eine Schritt für Schritt Erklärung was im oberen Beispiel passiert:

  1. Wir erstellen eine React Ref mit dem Aufruf von React.createRef und weisen es der ref Variable zu.
  2. Wir geben unsere ref an <FancyButton ref={ref}> weiter, in dem wir diese als JSX Attribut definieren.
  3. React gibt die ref der (props, ref) => ... Funktion innerhalb von forwardRef als zweites Argument weiter.
  4. Mit der Definition als JSX Attribut, leiten wir das ref Argument weiter zum <button ref={ref}>.
  5. When das Ref zugewiesen ist, zeigt ref.current auf den <button> DOM-Knoten.

Hinweis

Das zweite ref Argument existiert nur dann, wenn du eine Komponente mit React.forwardRef Aufruf definierst. Normale Funktion- oder Klassenkomponenten erhalten das ref Argument nicht, es ist auch nicht in den Props verfügbar.

Das Weiterleiten von Refs ist nicht nur auf DOM-Komponenten limitiert. Du kannst auch Refs zu Instanzen von Klassenkomponenten weiterleiten.

Hinweis für Betreiber von Komponentenbibliotheken

Wenn du anfängst forwardRef in einer Komponentenbibliothek zu nutzen, solltest du es als funktionsgefährdende Änderung behandeln und eine neue Major-Version deiner Bibliothek veröffentlichen. Deine Bibliothek wird höchstwahrscheinlich ein wahrnehmbar anderes Verhalten aufweisen (z.B. Zuordnung von Refs, welche Typen werden exportiert), dies könnte negative Auswirkungen auf andere Anwendungen und Bibliotheken haben, die auf das alte Verhalten angewiesen sind.

Bedingte Anwendung von React.forwardRef bei Vorhandensein, ist ebenso aus den gleichen Gründen nicht ratsam: es verändert das Verhalten deiner Bibliothek und könnte negative Auswirkungen auf die Anwendungen deiner User haben, wenn sie ein React-Upgrade durchführen.

Weiterleiten von Refs in Higher-Order-Komponenten

Diese Technik kann besonders in Verbindung mit Higher-Order-Komponenten (auch bekannt als HOC) nützlich sein. Beginnen wir mit einer Beispiel-HOC, welche die Eigenschaften einer Komponente in die Konsole loggt:

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('Alte Eigenschaften:', prevProps);
      console.log('Neue Eigenschaften:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}

Die “logProps” HOC gibt alle props an die Komponente durch, die von ihr umschlossen wird, aus diesem Grund wird die gerenderte Ausgabe gleich sein. Wir können zum Beispiel diese HOC nutzen, um alle Eigenschaften zu loggen, die an unsere “fancy button” Komponente weitergegeben werden:

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}

// Anstatt die FancyButton Komponente zu exportieren, exportieren wir LogProps.
// Es wird trotzdem der FancyButton gerendert.
export default logProps(FancyButton);

Folgendes muss im oberen Beispiel beachtet werden: es werden keine Refs weitergegeben. Das ist der Tatsache zu entnehmen, dass ref keine Eigenschaft ist. Ähnlich dem key, wird es anders von React behandelt. Wenn du eine Ref zu einer HOC hinzufügst, wird diese die äußerste Container-Komponente und nicht die umschlossene Komponente referenzieren.

Dies bedeutet, dass Refs die für unsere FancyButton Komponente bestimmt waren, eigentlich der LogProps Komponente zugewiesen sein werden:

import FancyButton from './FancyButton';

const ref = React.createRef();

// Die FancyButton Komponente die wir importieren ist die LogProps HOK.
// Auch wenn die gerenderte Ausgabe gleich bleibt,
// wird unsere Ref auf `LogProps` statt auf die innere FancyButton Komponente verweisen!
// Dies bedeutet, dass wir z.B. kein ref.current.focus() aufrufen können.
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;

Glücklicherweise, können wir Refs explizit an die innere FancyButton Komponente weiterleiten, in dem wir die React.forwardRef API benutzen. React.forwardRef akzeptiert eine render-Funktion, die props und ref Eigenschaften enthält und einen React-Knoten zurückgibt. Beispiel:

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Weise die benutzerdefinierte Eigenschaft "forwardRef" als Ref zu
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // Beachte das zweite Attribut "ref", welches von React.forwardRef bereitgestellt wird.
  // Wir können es an LogProps wie eine reguläre Eigenschaft weiterleiten, z.B. "forwardRef"
  // Und es kann einer Komponente zugewiesen werden.
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

Anzeigen von benutzerdefinierten Namen in DevTools

React.forwardRef akzeptiert eine render-Funktion. React DevTools nutzt diese Funktion um zu bestimmen, was für die Ref-weiterleitende Komponente angezeigt werden soll.

Zum Beispiel, folgende Komponente wird als ”ForwardRef” in DevTools aufscheinen.

const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});

Wenn du die render-Funktion benennst, wird dieser auch innerhalb von DevTools inkludiert (z.B. ”ForwardRef(myFunction)”):

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);

Du kannst sogar die Eigenschaft displayName setzen, um die umschlossene Komponente zu inkludieren.

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // Weise dieser Komponente einen aussagekräftigen Namen in DevTools zu.
  // z.B. "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}