Engineering

Entwickeln einer Suchfunktion für Anwendungen mit React und Elastic App Search

Wenn Nutzer:innen etwas suchen, möchten sie relevante Ergebnisse angezeigt bekommen. Relevante Ergebnisse sind aber nur einer von mehreren Aspekten, wenn es darum geht, Besucher:innen zu überzeugen; die Suche muss sich für sie auch gut anfühlen. Die Suche sollte schnell sein, dynamisch auf die bereits eingegebenen Zeichen reagieren und sich intelligent und effektiv anfühlen.

In diesem Tutorial zeigen wir Ihnen, wie Sie mit React und dem JavaScript-Client Elastic App Search eine flüssige und robuste Suchfunktion für Anwendungen entwickeln können. Am Ende haben Sie eine gut aussehende, relevante, React-basierte Anwendung, mit der es möglich ist, verschiedene npm-Pakete in Echtzeit zu durchsuchen – mit Sortierung nach Facetten und Status als Teil des URI.

Den vollständigen Code finden Sie auf GitHub.


Hallo,

es gibt einen neueren Artikel zum noch schnelleren Entwickeln leistungsfähiger Suchen mithilfe der Elastic-eigenen Open-Source-Bibliothek Search UI.

Neueren Artikel lesen


Voraussetzungen

Um fortfahren zu können, benötigen Sie Folgendes:

  1. eine aktuelle Version von Node.js
  2. eine aktuelle Version von npm
  3. ein Elastic App Search Service-Konto oder eine gültige kostenlose 14-tägige Testversion
  4. ca. 30 Minuten Zeit

App Search – Einführung

Jede Anwendung braucht für ihr Funktionieren Daten. Facebook war so erfolgreich, weil es „Freunde“-Daten auf interessante Weise präsentierte. eBay war ursprünglich die einfachste Möglichkeit, Gebrauchtes zu finden und zu kaufen. Wikipedia hat dafür gesorgt, dass wir einfach alles nachlesen können.

Die Aufgabe von Anwendungen ist es, Datenprobleme zu lösen. Und die Suche leistet dazu einen wichtigen Beitrag. Bei großen Anwendungen ist die Suche ein wichtiger Teil des Angebots – die Anwendung braucht eine Möglichkeit, Freunde, Produkte, Unterhaltungen oder Artikel zu finden. Je größer und interessanter das Angebot von Daten ist, desto beliebter wird die Anwendung sein. Das gilt vor allem dann, wenn die Suche relevant und lohnend ist.

Elastic App Search baut auf Elasticsearch auf, einer verteilten Open-Source-RESTful-Suchmaschine. Mit Elastic App Search erhalten Entwickler:innen Zugang zu einem robusten Satz API-Endpoints, die für die Bearbeitung von Suchanwendungsfällen in Premium-Anwendungen optimiert sind.

Starten eigener Engines

Erstellen Sie zunächst eine Engine innerhalb von App Search.

Engines ingestieren Objekte, um sie indexieren zu können. Die Objekte sind Ihre Daten – das Freundeprofil, das Produkt, die Wiki-Seite usw. Nach dem Ingestieren in App Search werden die Daten anhand eines flexiblen Schemas indexiert und für die Suche optimiert. Anschließend können wir verschiedene Client-Bibliotheken nutzen, um ein positives Sucherlebnis zu schaffen.

In unserem Beispiel hier nennen wir unsere Engine „node-modules“.

Nach dem Erstellen der Engine benötigen wir drei Dinge von der Seite Credentials:

  1. den Host-Identifier, erkennbar am Präfix „host-“
  2. einen privaten API-Schlüssel, erkennbar am Präfix „private-“
  3. einen öffentlichen Suchschlüssel, erkennbar am Präfix „search-“

Damit können wir das Projekt klonen, ins Verzeichnis wechseln, zum Zweig „starter“ gehen und eine npm-Installation durchführen:

$ git clone https://github.com/swiftype/app-search-demo-react.git
$ cd react-tutorial && git checkout starter && npm install

Hervorragend! Wir haben eine Anwendung vorbereitet und sind bereit. Für eine Suche braucht es aber Daten …

Ingestieren ~

In den meisten Fällen werden sich Ihre Objekte in einer Datenbank oder einer Backend-API befinden. Da es sich hier um ein Beispiel handelt, verwenden wir eine statische .json-Datei. Das Repository enthält zwei Skripts: init-data.js und index-data.js. Das erste ist ein Scraper, der verwendet wurde, um ordnungsgemäß formatierte „node-module“-Daten von npm zu erhalten. Die Daten befinden sich in der Datei node-modules.json. Das zweite Skript ist ein Indexer, der diese Daten in Ihre App Search Engine ingestieren wird, damit sie indexiert werden können.

Um das Indexer-Skript ausführen zu können, müssen wir auch unseren Host-Identifier und unseren privaten API-Schlüssel übergeben.

$ REACT_APP_HOST_IDENTIFIER={Your Host Identifier} \
REACT_APP_API_KEY={Your Private API Key} \
npm run index-data

Die Objekte werden in Stapeln von 100 schnell an unsere App Search Engine gesendet und der Index wird erstellt.

Wir sollten nun ein Dashboard für unsere neu erstellte Engine mit ca. 9.500 npm-Paketen haben, die als Dokumente indexiert sind. An dieser Stelle kann es helfen, mit den Daten etwas herumzuspielen, um sich mit deren Inhalt vertraut zu machen.

app_search_engine_overview.png

React-ionsfähig

Unsere Engine ist befüllt und einsatzbereit, wir können also damit beginnen, unsere Kernanwendung zusammenzubasteln.

$ npm start

Wenn wir npm aus dem Projektverzeichnis heraus starten, öffnet sich das React-Boilerplate. Sein Aussehen wird von App.css bestimmt und wir können es anpassen, damit es unseren Anforderungen entspricht.

Schon bald werden wir ein Suchfeld benötigen, in das wir unsere Suchabfragen eingeben können. Die Nutzer:innen werden nach diesen nützlichen Rechtecken suchen, die ihnen aus Suchmaschinen und Browsern vertraut sind: Suchbegriff hier eingeben, um die Suche zu starten.

//App.css
...
.App-search-box {
height: 40px;
width: 500px;
font-size: 1em;
margin-top: 10px;
}

Außerdem müssen wir unsere Zugangsdaten für App Search an einem sicheren Ort ablegen, beispielsweise in einer .env-Datei.

Erstellen Sie eine solche Datei im Stammverzeichnis des Projekts und füllen Sie sie wie folgt aus:

//.env
REACT_APP_HOST_IDENTIFIER={Ihr Host-Identifier mit dem Präfix host-}
REACT_APP_SEARCH_KEY={Ihr öffentlicher Suchschlüssel mit dem Präfix search-}

Jetzt sind die Variablen sicher untergebracht und wir können damit beginnen, unsere Suchlogik zu schreiben.

Mit dem Suchen beginnen

Die Kernlogik wird sich in der Datei App.js befinden. Diese Datei – wie auch die meisten anderen Starter-Dateien – wurde von create-react-app erstellt, einem Tool, das dabei hilft, React-Anwendungen ohne jegliche Konfiguration zu laden. Bevor wir eine Logik zum Testen der Suche schreiben, müssen wir die Client-Bibliothek „Swiftype App Search JavaScript“ installieren:

$ npm install --save swiftype-app-search-javascript

Fügen Sie den folgenden Code in App.js ein. Er wird eine einfache Suche durchführen.

Als unseren Beispielsuchbegriff werden wir „foo“ hartcodieren:

import * as SwiftypeAppSearch from "swiftype-app-search-javascript";
const client = SwiftypeAppSearch.createClient({
hostIdentifier: process.env.REACT_APP_HOST_IDENTIFIER,
apiKey: process.env.REACT_APP_SEARCH_KEY,
engineName: "node-modules"
});
//Wir können nach allem suchen – foo ist lediglich ein Beispiel.
const query = "foo";
const options = {};
client.search(query, options)
.then(resultList => console.log(resultList))
.catch(error => console.log(error))

Der Browser aktualisiert sich und erstellt über console.log ein resultList-Array. Zur Untersuchung des Array können wir die Entwicklerkonsole unseres Browsers öffnen. Wir können ein paar andere Abfragen ausprobieren, indem wir „foo“ durch eine andere Zeichenfolge ersetzen. Sobald die Abfrage geändert wurde und die Seite sich aktualisiert hat, können wir sehen, wie sich der Ergebnissatz verändert hat.

Super, und schon können wir unsere node-module-Instanzen durchsuchen.

Ergebnisse aus dem Versteck holen

Wir haben ein einfaches Muster für die Suche geschaffen, aber die Ergebnisse sind wenig hilfreich, da sie in einem console.log versteckt sind. Daher entfernen wir jetzt den einfachen React-Stil und unseren vorherigen Code und erweitern das Ganze.

Wir werden Folgendes erstellen:

  1. Eine Statusvariable, die eine Antworteigenschaft enthalten wird.
  2. Eine performQuery-Methode, die mit client.search App Search abfragen und die Suchergebnisse in der Eigenschaft „response“ speichern wird.
  3. Ein componentDidMount-Lifecycle-Hook, der beim Laden der Anwendung einmalig ausgeführt wird. Wir werden wieder „foo“ als Suchabfrage verwenden, wir könnten aber auch nach jedem anderen Begriff suchen.
  4. Strukturiertes HTML, um die resultierende Datenausgabe und die Gesamtzahl der Ergebnisse zu speichern.
//App.js
// ... Gekürzt!
class App extends Component {
state = {
// Neue „state“-Eigenschaft, die die letzte Abfrageantwort speichert
response: null
};
componentDidMount() {
/*Durch diesen Aufruf in componentDidMount wird sichergestellt, dass die Ergebnisse
beim ersten Laden der App auf dem Bildschirm angezeigt werden*/
this.performQuery("foo");
}
// Methode zum Ausführen einer Abfrage und Speichern der Antwort
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
// Diese erst einmal hinzufügen, um die vollständige Antwort ansehen zu können
console.log(response);
this.setState({ response });
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response} = this.state;
if (!response) return null;
return (


Node Module Search



{/*Zeigt die Gesamtzahl der Ergebnisse für diese Abfrage an*/}

{response.info.meta.page.total_results} Results


{/*Iteriert über die Ergebnisse und zeigt deren Namen und Beschreibungen an*/}
{response.results.map(result => (

Name: {result.getRaw("name")}


Description: {result.getRaw("description")}



))}

);
}
}
// ... Gekürzt!

In dem Moment, in dem wir auf „Speichern“ klicken, erscheinen die Ergebnisse in http://localhost:3000: – 27 Ergebnisse und ein paar toll klingende Module. Wenn etwas schief gelaufen ist, können wir in der Konsole nachsehen, da wir zwei console.log-Dateien im Code untergebracht haben.

Das Auge sucht mit

Wir haben „foo“ in unsere Abfragen hartcodiert eingebaut. Eine Suchfunktion ist dann am wertvollsten, wenn sie mit einem frei eingebbaren Ausdruck beginnt. Jetzt, wo Sie ein hervorragendes Sucherlebnis entwickelt haben, können Sie es für die gebräuchlicheren Ausdrücke optimieren und dabei die relevantesten Ergebnissätze kuratieren. Alles beginnt mit einer leeren Leinwand: dem Suchfeld.

Um ein funktionierendes Suchfeld zu erstellen, fügen wir „state“ eine Eigenschaft namens „queryString“ hinzu. Damit „queryString“ mit neuen Zeichenfolgen aktualisiert wird, erstellen wir eine updateQuery-Methode; wir nutzen einen onChange-Handler, um „queryString“ zu aktualisieren und jedes Mal, wenn sich der Text im Suchfeld ändert, eine neue Suche auszulösen.

Unsere vollständige App-Klasse sieht damit wie folgt aus:

//src/App.js
// ... Gekürzt!
class App extends Component {
state = {
// Neue „state“-Eigenschaft, die den Wert im Suchfeld verfolgt
queryString: "",
response: null
};
componentDidMount() {
// Die hartcodierte Suche für „node“ entfernen
this.performQuery(this.state.queryString);
}
// Verarbeitet bei jeder Eingabe von Text in das Suchfeld das onChange-Ereignis.
updateQuery = e => {
const queryString = e.target.value;
this.setState(
{
queryString // Nutzerseitig eingegebene Abfragezeichenfolge speichern
},
() => {
this.performQuery(queryString); // Neue Suche auslösen
}
);
};
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response, queryString} = this.state;
if (!response) return null;
return (


Node Module Search



{/*Suchfeld, das mit unserem Abfragezeichenfolgewert und onChange verbunden ist
handler*/}

);
}
}
// ... Gekürzt!

Debounce-Funktion integrieren

Bei dieser Iteration wird jedes Mal, wenn eine Änderung im Feld festgestellt wird, eine Suche durchgeführt – das kann für unsere Systeme sehr aufwendig werden. Um dem vorzubauen, bauen wir eine _debounce_-Funktion von Lodash ein.

$ npm install --save lodash

„Debounce“ ist eine Methode zur Begrenzung der Anzahl der eingehenden Anfragen auf der Grundlage einer vorgegebenen Zahl von Millisekunden. Nutzer:innen überlegen, wie sie ihre Abfragen formulieren, machen Tippfehler oder tippen sehr schnell... und deshalb macht es wenig Sinn, nach jeder erkannten Änderung eine Abfrage zu starten.

Durch Einbindung unserer performQuery-Methode in eine Debounce-Funktion von Lodash können wir ein Limit von 200 ms festlegen. Das ist die Zeit, die eingabelos vergehen muss, bevor die nächste Suchanfrage beginnt:

//App.js
// ... Gekürzt!
import { debounce } from "lodash"; // Importiert debounce
// ... Gekürzt!
performQuery = debounce(queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
}, 200); // 200 Millisekunden.
// ... Gekürzt!

Abgesehen davon, dass wir unseren Servern eine Pause gönnen, kann ein solches Limit dazu beitragen, die Nutzeranfragen „glatter“ zu machen. Das bringt eine Menge! Schließlich geht es darum, dass sich die Suche gut anfühlt.

Wie geht es weiter?

Der Anfang für ein hochwertiges, „React-basiertes“ Sucherlebnis ist gemacht. Wir können aber das positive Erlebnis noch um viele tolle Aspekte ergänzen. Wir können Styling hinzufügen oder dynamische App Search-Funktionen wie Facetten, Kuratierungen oder Relevanz-Tuning implementieren. Oder wir können uns die Analytics API Suite ansehen, um wertvolle Einblicke in die Suchaktivitäten der Nutzer:innen zu erhalten.

Wenn wir richtig tief einsteigen wollen, finden wir in der README im Hauptzweig eine Erweiterung des Tutorials mit Informationen zum Einrichten einer URI-basierten Statusverwaltung sowie zum Hinzufügen von Styling-Optionen, Paginierung und Filter- und Facettierungsfunktionen. Mit ein paar stilistischen Anpassungen können wir so die Grundlage für ein hochklassiges Sucherlebnis schaffen.

Zusammenfassung

Bislang war es alles andere als einfach, eine relevante und lohnende Anwendungssuche auf die Beine zu stellen. Mit Elastic App Search steht eine praktische verwaltete Methode zur Verfügung, mit der Sie Webanwendungen mit einer hilfreichen, justierbaren Suche versehen können. Und das Beste daran ist, dass dank des eleganten und intuitiven Dashboards sowohl technisches Personal als auch Endnutzer:innen wichtige Funktionen einfach verwalten können. Interessiert? Dann probieren Sie App Search 14 Tage lang kostenlos aus – keine Kreditkarte erforderlich.