Zum Inhalt springen
Astrid

Inline-Handler in JSX

Inline-Handler in JSX

Bei der stories-Liste handelt es sich um eine statuslose Variable. Wir filtern diese mit mithilfe der Suchfunktion. Dabei bleibt die Liste selbst erhalten. Wenn wir den Filter entfernen, werden erneut alle Elemente angezeigt. Dieser bewirkt nur eine vorübergehende Änderung der Anzeige.

Um die Kontrolle über die Liste zu haben, weisen wir ihr im nächsten Schritt einen Status zu. Hierzu verwenden wir den useState-Hook. Die zurückgegebenen Werte sind der aktuelle Status (stories) und die Statusaktualisierungsfunktion (setStories). Wir verwenden den benutzerdefinierten Hook useSemiPersistentState nicht, da wir die Liste nur innerhalb eines Aufrufs zwischenspeichern --- bei jedem neuen greifen wir auf die initiale zu.

# start-insert
const initialStories = [
  {
    title: 'React',
    ...
  },
  {
    title: 'Redux',
    ...
  },
];
# end-insert

const useSemiPersistentState = (key, initialState) => { ... };

const App = () => {
  const [searchTerm, setSearchTerm] = ...

# start-insert
  const [stories, setStories] = React.useState(initialStories);
# end-insert

  ...
};

Die Anwendung verhält sich zunächst unverändert. Die jetzt von useState zurückgegebenen stories werden weiterhin in searchedStories gefiltert und angezeigt. Als Nächstes bearbeiten wir die Liste --- wir entfernen ein Element:

const App = () => {
  ...

  const [stories, setStories] = React.useState(initialStories);

# start-insert
  const handleRemoveStory = item => {
    const newStories = stories.filter(
      story => item.objectID !== story.objectID
    );

    setStories(newStories);
  };
# end-insert

  ...

  return (
    <div>
      <h1>My Hacker Stories</h1>

      ...

      <hr />

# start-insert
      <List list={searchedStories} onRemoveItem={handleRemoveStory} />
# end-insert
    </div>
  );
};

Der Callback-Handler handleRemoveStory in der App-Komponente empfängt ein Element, um es zu entfernen. Außerdem filtert er weiterhin die aktuelle Liste. Die zurückgegebenen stories werden dabei als neuer Status festgelegt. List übergibt die Funktion onRemoveItem/ handleRemoveStory an untergeordnete Komponenten --- genutzt wird onRemoveItem bisher nicht:

# start-insert
const List = ({ list, onRemoveItem }) =>
# end-insert
  list.map(item => (
    <Item
      key={item.objectID}
      item={item}
# start-insert
      onRemoveItem={onRemoveItem}
# end-insert
    />
  ));

Letztendlich verwenden wir die Funktion onRemoveItem in der Item-Komponente. Auf diese Art und Weise wird das item über List an den Handler handleRemoveStory übergeben. Ausgelöst wird das eigentliche Ereignis über eine Schaltfläche:

# start-insert
const Item = ({ item, onRemoveItem }) => {
  const handleRemoveItem = () => {
    onRemoveItem(item);
  };
# end-insert

  return (
    <div>
      <span>
        <a href={item.url}>{item.title}</a>
      </span>
      <span>{item.author}</span>
      <span>{item.num_comments}</span>
      <span>{item.points}</span>
# start-insert
      <span>
        <button type="button" onClick={handleRemoveItem}>
          Dismiss
        </button>
      </span>
# end-insert
    </div>
  );
};

Eine Alternative wäre es, nur die objectID des Elements zu übergeben. Das ist alles, was wir im Callback-Handler der App-Komponente momentan benötigen. Dies wäre aber nicht zukunftssicher. Unter Umständen benötigt der Handler später einmal mehr Informationen, um das zu löschende Item zu entfernen. Deshalb übergeben wir schon jetzt das vollständige Element und nicht nur die Kennung objectID, wenn wir den Handler onRemoveItem aufrufen.

Bisher haben wir Folgendes umgesetzt: Wir haben die Liste stories mithilfe des useState-Hook mit einem Status versehen, das Suchwort als Eigenschaft (Props) an die List-Komponente weitergegeben, einen Callback-Handler (handleRemoveStory) und die Funktion (handleRemoveItem) implementierte. Da ein Handler eine Funktion ist und in diesem Fall nichts zurückgibt, entfernen wir der Vollständigkeit halber den Blockkörper und nutzen die knappe Schreibweise.

const Item = ({ item, onRemoveItem }) => {
# start-insert
  const handleRemoveItem = () =>
    onRemoveItem(item);
# end-insert

  ...
};

Die knappe Schreibweise verbessert den Quellcode an dieser Stelle nicht wesentlich. Manchmal überarbeite ich Handler in einer Funktionskomponente von einer Pfeilfunktion zurück zur normalen Blockansicht, um die Komponente weiterhin unkompliziert zu bearbeiten:

const Item = ({ item, onRemoveItem }) => {
# start-insert
  function handleRemoveItem() {
    onRemoveItem(item);
  }
# end-insert

  ...
};

In diesem Abschnitt haben wir bisher Eigenschaften (Props), Handler, Callback-handler und Status angewendet. Soweit ist nichts Neues hinzugekommen. Kommen wir jetzt zum eigentlichen Thema des Abschnitts --- nachfolgend werden wir uns mit Inline-Handlern befassen. Mit diesen rufen wir eine Funktion direkt in JSX auf. Es gibt zwei Möglichkeiten, um onRemoveItem als Inline-Handler zu verwenden. Sehen wir uns zunächst die JavaScript-Methode bind() an:

const Item = ({ item, onRemoveItem }) => (
  <div>
    <span>
      <a href={item.url}>{item.title}</a>
    </span>
    <span>{item.author}</span>
    <span>{item.num_comments}</span>
    <span>{item.points}</span>
    <span>
# start-insert
      <button type="button" onClick={onRemoveItem.bind(null, item)}>
# end-insert
        Dismiss
      </button>
    </span>
  </div>
);

bind() ermöglicht es uns, Argumente direkt an eine Funktion zu binden. Diese Argumente werden angewendet, wenn die Funktion aufgerufen wird. Die bind()-Methode gibt eine neue Funktion mit dem angehängten gebundenen Argument zurück.

Die zweite und beliebteste Alternative ist die Verwendung einer umgebenden Pfeilfunktion. Mit dieser ist es möglich, Argumente wie item zu übergeben:

const Item = ({ item, onRemoveItem }) => (
  <div>
    <span>
      <a href={item.url}>{item.title}</a>
    </span>
    <span>{item.author}</span>
    <span>{item.num_comments}</span>
    <span>{item.points}</span>
    <span>
# start-insert
      <button type="button" onClick={() => onRemoveItem(item)}>
# end-insert
        Dismiss
      </button>
    </span>
  </div>
);

Nachfolgend zeige ich dir eine vermeintlich schnelle Lösung. Es kommt vor, dass wir den knappen Funktionskörper einer Funktionskomponente nicht in einen Blockkörper umgestalten, um einen Handler zwischen Funktionssignatur und Return-Anweisung einzufügen. Diese Methode ist knapper. Das ist ein Vorteil. Nachteilig ist, dass sie schwieriger zu debuggen ist, denn die JavaScript-Logik ist unter Umständen im JSX verborgen. Dies wird in vollem Umfang deutlich, wenn die umgebende Pfeilfunktion mehr als eine Zeile kapselt, indem ein Blockkörper anstelle eines knappen Körpers verwendet wird. Vermeide dies:

const Item = ({ item, onRemoveItem }) => (
  <div>
    ...
    <span>
      <button
        type="button"
        onClick={() => {
          // Erledige eine Aufgabe

          // Hinweis: Vermeide die Verwendung komplexer Logik innerhalb von JSX

          onRemoveItem(item);
        }}
      >
        Dismiss
      </button>
    </span>
  </div>
);

Alle drei Handler-Versionen sind geeignete Lösungen. Der normale Handler nutzt für die Implementierungsdetails den Blockkörper der Funktionskomponente. Die beiden Inline-Handler verwenden JSX.

Übungen: