Einen Chat erstellen Mit React Hooks in 100 Zeilen Code

Avatar of Akash Joshi
Akash Joshi on

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wir haben uns bereits mit React Hooks beschäftigt, hier bei CSS-Tricks. Ich habe einen Artikel, der sie vorstellt, der auch veranschaulicht, wie man sie zur Erstellung von Komponenten durch Funktionen verwendet. Beide Artikel sind gute High-Level-Übersichten darüber, wie sie funktionieren, aber sie eröffnen auch viele Möglichkeiten.

Das werden wir also in diesem Artikel tun. Wir werden *sehen*, wie Hooks unseren Entwicklungsprozess einfacher und schneller machen, indem wir eine Chat-Anwendung erstellen.

Insbesondere erstellen wir eine Chat-Anwendung mit Create React App. Dabei werden wir eine Auswahl von React Hooks verwenden, um den Entwicklungsprozess zu vereinfachen und viel Boilerplate-Code zu entfernen, der für die Arbeit unnötig ist.

Es gibt mehrere Open-Source-React-Hooks, die wir ebenfalls nutzen werden. Diese Hooks können direkt genutzt werden, um Funktionen zu erstellen, die sonst mehr Code erfordert hätten. Sie folgen auch allgemein anerkannten Standards für jede Funktionalität. Dadurch wird die Effizienz beim Schreiben von Code erhöht und sichere Funktionalitäten bereitgestellt.

Schauen wir uns die Anforderungen an

Die Chat-Anwendung, die wir erstellen werden, wird die folgenden Funktionen haben

  • Eine Liste vergangener Nachrichten vom Server abrufen
  • Verbindung zu einem Raum für Gruppenchats herstellen
  • Updates erhalten, wenn Personen einen Raum verlassen oder betreten
  • Nachrichten senden und empfangen

Wir gehen von einigen Annahmen aus, während wir beginnen

  • Wir werden den Server, den wir verwenden werden, als Blackbox betrachten. Machen Sie sich keine Sorgen, ob er perfekt funktioniert, da wir über einfache Sockets mit ihm kommunizieren werden.
  • Alle Stile sind in einer einzigen CSS-Datei enthalten und können in das src-Verzeichnis kopiert werden. Alle im App verwendeten Stile sind im Repository verlinkt.

Vorbereitung für die Arbeit

OK, wir wollen unsere Entwicklungsumgebung vorbereiten, um mit dem Schreiben von Code zu beginnen. Zunächst einmal benötigt React sowohl Node als auch npm. Sie können sie hier einrichten.

Lassen Sie uns ein neues Projekt im Terminal starten

npx create-react-app socket-client
cd socket-client
npm start

Jetzt sollten wir in der Lage sein, unter https://:3000 im Browser zu navigieren und die Standard-Willkommensseite für das Projekt zu sehen.

Von hier aus werden wir die Arbeit nach den Hooks aufteilen, die wir verwenden. Dies sollte uns helfen, die Hooks zu verstehen, während wir sie praktisch anwenden.

Verwendung des useState Hooks

Der erste Hook, den wir verwenden werden, ist useState. Er ermöglicht es uns, den Zustand innerhalb unserer Komponente zu verwalten, anstatt beispielsweise eine Klasse mit this.state schreiben und initialisieren zu müssen. Daten, die konstant bleiben, wie der Benutzername, werden in useState-Variablen gespeichert. Dies stellt sicher, dass die Daten leicht verfügbar bleiben und erfordert dabei deutlich weniger Code.

Der Hauptvorteil von useState ist, dass er automatisch in der gerenderten Komponente reflektiert wird, wann immer wir den Zustand der App aktualisieren. Wenn wir reguläre Variablen verwenden würden, würden diese nicht als Zustand der Komponente betrachtet werden und müssten als Props übergeben werden, um die Komponente neu zu rendern. Also schneiden wir wieder viel Arbeit aus und optimieren die Dinge dabei.

Der Hook ist direkt in React integriert, daher können wir ihn mit einer einzigen Zeile importieren

import React, { useState } from 'react';

Wir werden eine einfache Komponente erstellen, die "Hallo" zurückgibt, wenn der Benutzer bereits angemeldet ist, oder ein Anmeldeformular, wenn der Benutzer abgemeldet ist. Wir überprüfen dafür die id-Variable.

Unsere Formularübermittlungen werden von einer Funktion namens handleSubmit behandelt. Sie prüft, ob das Namensfeld ausgefüllt ist. Wenn ja, setzen wir die Werte id und room für diesen Benutzer. Andernfalls geben wir eine Meldung aus, die den Benutzer daran erinnert, dass das Namensfeld erforderlich ist, um fortzufahren.

// App.js

import React, { useState } from 'react';
import './index.css';

export default () => {
  const [id, setId] = useState("");
  const [nameInput, setNameInput] = useState("");
  const [room, setRoom] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    if (!nameInput) {
      return alert("Name can't be empty");
    }
    setId(name);
    socket.emit("join", name, room);
  };

  return id !== '' ? (
    <div>Hello</div>
  ) : (
    <div style={{ textAlign: "center", margin: "30vh auto", width: "70%" }}>
      <form onSubmit={event => handleSubmit(event)}>
        <input
          id="name"
          onChange={e => setNameInput(e.target.value.trim())}
          required
          placeholder="What is your name .."
        />
        <br />
        <input
          id="room"
          onChange={e => setRoom(e.target.value.trim())}
          placeholder="What is your room .."
        />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

So verwenden wir den useState-Hook in unserer Chat-Anwendung. Wiederum importieren wir den Hook aus React, erstellen Werte für die Benutzer-ID und den Chatraum, setzen diese Werte, wenn der Benutzer angemeldet ist, und geben ein Anmeldeformular zurück, wenn der Benutzer abgemeldet ist.

Verwendung des useSocket Hooks

Wir werden einen Open-Source-Hook namens useSocket verwenden, um eine Verbindung zu unserem Server aufrechtzuerhalten. Im Gegensatz zu useState ist dieser Hook nicht in React integriert, daher müssen wir ihn zu unserem Projekt hinzufügen, bevor wir ihn in die App importieren.

npm add use-socket.io-client

Die Serververbindung wird über die React Hooks-Version der socket.io-Bibliothek aufrechterhalten, was eine einfachere Methode ist, Websocket-Verbindungen mit einem Server aufrechtzuerhalten. Wir verwenden sie zum Senden und Empfangen von Echtzeitnachrichten sowie zur Verwaltung von Ereignissen, wie dem Beitritt zu einem Raum.

Die Standard-socket.io-Clientbibliothek hat globale Deklarationen, d.h. die von uns definierte Socket-Variable kann von jeder Komponente verwendet werden. Unsere Daten können jedoch von überall manipuliert werden und wir wissen nicht, woher diese Änderungen stammen. Socket-Hooks kontern dies, indem sie Hook-Definitionen auf Komponentenebene einschränken, was bedeutet, dass jede Komponente für ihre eigene Datenübertragung verantwortlich ist.

Die grundlegende Verwendung von useSocket sieht so aus

const [socket] = useSocket('socket-url')

Wir werden einige Socket-APIs verwenden, wenn wir weitermachen. Zur Referenz sind sie alle in der socket.io-Dokumentation aufgeführt. Aber vorerst importieren wir den Hook, da wir ihn bereits installiert haben.

import useSocket from 'use-socket.io-client';

Als Nächstes müssen wir den Hook initialisieren, indem wir uns mit unserem Server verbinden. Dann protokollieren wir den Socket in der Konsole, um zu überprüfen, ob er ordnungsgemäß verbunden ist.

const [id, setId] = useState('');
const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');

socket.connect();
console.log(socket);

Öffnen Sie die Browserkonsole und die URL im Ausschnitt sollte protokolliert werden.

Verwendung des useImmer Hooks

Unsere Chat-App nutzt den useImmer Hook, um den Zustand von Arrays und Objekten zu verwalten, ohne den ursprünglichen Zustand zu verändern. Er kombiniert useState und Immer, um eine unveränderliche Zustandsverwaltung zu ermöglichen. Dies ist nützlich für die Verwaltung von Listen von Online-Personen und anzuzeigenden Nachrichten.

Die Verwendung von Immer mit useState ermöglicht es uns, ein Array oder Objekt zu ändern, indem wir einen neuen Zustand aus dem aktuellen Zustand erstellen und gleichzeitig Mutationen am aktuellen Zustand verhindern. Dies bietet uns mehr Sicherheit, indem wir den aktuellen Zustand intakt lassen und den Zustand basierend auf verschiedenen Bedingungen manipulieren können.

Auch hier arbeiten wir mit einem Hook, der nicht in React integriert ist. Importieren wir ihn also in das Projekt

npm add use-immer

Die grundlegende Verwendung ist ziemlich einfach. Der erste Wert im Konstruktor ist der aktuelle Zustand und der zweite Wert ist die Funktion, die diesen Zustand aktualisiert. Der useImmer Hook nimmt dann die Startwerte für den aktuellen Zustand.

const [data, setData] = useImmer(default_value)

Verwendung von setData

Beachten Sie die Funktion setData im letzten Beispiel? Wir verwenden sie, um eine Kopie der aktuellen Daten zu erstellen, die wir verwenden können, um die Daten sicher zu manipulieren und sie als nächsten Zustand zu verwenden, wenn Änderungen unveränderlich werden. Somit bleiben unsere ursprünglichen Daten erhalten, bis wir unsere Funktionen ausgeführt haben und es absolut klar ist, die aktuellen Daten zu aktualisieren.

setData(draftState => { 
  draftState.operation(); 
});

// ...or

setData(draft => newState);

// Here, draftState is a copy of the current data

Verwendung des useEffect Hooks

Alles klar, wir sind wieder bei einem Hook angelangt, der direkt in React integriert ist. Wir werden den useEffect Hook verwenden, um einen Codeabschnitt nur dann auszuführen, wenn die Anwendung geladen wird. Dies stellt sicher, dass unser Code nur einmal ausgeführt wird und nicht jedes Mal, wenn die Komponente mit neuen Daten neu gerendert wird, was gut für die Leistung ist.

Alles, was wir tun müssen, um den Hook zu verwenden, ist, ihn zu importieren – keine Installation erforderlich!

import React, { useState, useEffect } from 'react';

Wir benötigen eine Komponente, die eine Nachricht oder ein Update rendert, basierend auf dem Vorhandensein oder Fehlen einer Absender-ID im Array. Da wir kreative Leute sind, nennen wir diese Komponente Messages.

const Messages = props => props.data.map(m => m[0] !== '' ? 
(<li key={m[0]}><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>) 
: (<li key={m[1]} className="update">{m[1]}</li>) );

Lassen Sie uns unsere Socket-Logik in useEffect einfügen, damit wir nicht die gleichen Nachrichtensets wiederholt duplizieren, wenn eine Komponente neu gerendert wird. Wir definieren unseren Nachrichten-Hook in der Komponente, verbinden uns mit dem Socket und richten dann Listener für neue Nachrichten und Updates im useEffect Hook selbst ein. Wir werden auch Update-Funktionen innerhalb der Listener einrichten.

const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');      
socket.connect();

const [messages, setMessages] = useImmer([]);
useEffect(()=>{
  socket.on('update', message => setMessages(draft => {
    draft.push(['', message]);
  }));

  socket.on('message que',(nick, message) => {
    setMessages(draft => {
      draft.push([nick, message])
    })
  });
},0);

Als weiteren Leckerbissen fügen wir eine "Join"-Nachricht hinzu, wenn Benutzername und Raumname korrekt sind. Dies löst die restlichen Event-Listener aus und wir können vergangene Nachrichten, die in diesem Raum gesendet wurden, sowie alle erforderlichen Updates empfangen.

// ...
  socket.emit('join', name, room);
};

return id ? (
  <section style={{ display: "flex", flexDirection: "row" }}>
      <ul id="messages">
        <Messages data={messages} />
      </ul>
      <ul id="online">
        {" "}
        &#x1f310; : <Online data={online} />{" "}
      </ul>
      <div id="sendform">
        <form onSubmit={e => handleSend(e)} style={{ display: "flex" }}>
          <input id="m" onChange={e => setInput(e.target.value.trim())} />
          <button style={{ width: "75px" }} type="submit">
            Send
          </button>
        </form>
      </div>
    </section>
) : (
// ...

Die letzten Schliff

Wir haben nur noch wenige Handgriffe, um unsere Chat-App fertigzustellen. Insbesondere brauchen wir noch

  • Eine Komponente zur Anzeige von Online-Personen
  • Einen useImmer Hook dafür mit einem Socket-Listener
  • Einen Nachrichtenübermittlungs-Handler mit entsprechenden Sockets

All dies baut auf dem auf, was wir bisher abgedeckt haben. Ich werde den vollständigen Code für die Datei App.js einfügen, um zu zeigen, wie alles zusammenpasst.

// App.js

import React, { useState, useEffect } from 'react';
import useSocket from 'use-socket.io-client';
import { useImmer } from 'use-immer';

import './index.css';

const Messages = props => props.data.map(m => m[0] !== '' ? (<li><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>) : (<li className="update">{m[1]}</li>) );

const Online = props => props.data.map(m => <li id={m[0]}>{m[1]}</li>);

export default () => {
  const [id, setId] = useState('');
  const [nameInput, setNameInput] = useState('');
  const [room, setRoom] = useState('');
  const [input, setInput] = useState('');

  const [socket] = useSocket('https://open-chat-naostsaecf.now.sh');
  socket.connect();

  const [messages, setMessages] = useImmer([]);
  const [online, setOnline] = useImmer([]);

  useEffect(()=>{
    socket.on('message que',(nick,message) => {
      setMessages(draft => {
        draft.push([nick,message])
      })
    });

    socket.on('update',message => setMessages(draft => {
      draft.push(['',message]);
    }));

    socket.on('people-list',people => {
      let newState = [];
      for(let person in people){
        newState.push([people[person].id,people[person].nick]);
      }
      setOnline(draft=>{draft.push(...newState)});
      console.log(online)
    });

    socket.on('add-person',(nick,id)=>{
      setOnline(draft => {
        draft.push([id,nick])
      })
    });

    socket.on('remove-person',id=>{
      setOnline(draft => draft.filter(m => m[0] !== id))
    });

    socket.on('chat message',(nick,message)=>{
      setMessages(draft => {draft.push([nick,message])})
    });
  },0);

  const handleSubmit = e => {
    e.preventDefault();
    if (!nameInput) {
      return alert("Name can't be empty");
    }
    setId(name);
    socket.emit("join", name,room);
  };

  const handleSend = e => {
    e.preventDefault();
    if(input !== ''){
      socket.emit('chat message',input,room);
      setInput('');
    }
  };

  return id ? (
    <section style={{display:'flex',flexDirection:'row'}} >
      <ul id="messages"><Messages data={messages} /></ul>
      <ul id="online"> &#x1f310; : <Online data={online} /> </ul>
      <div id="sendform">
        <form onSubmit={e => handleSend(e)} style={{display: 'flex'}}>
            <input id="m" onChange={e=>setInput(e.target.value.trim())} /><button style={{width:'75px'}} type="submit">Send</button>
        </form>
      </div>
    </section>
  ) : (
    <div style={{ textAlign: 'center', margin: '30vh auto', width: '70%' }}>
      <form onSubmit={event => handleSubmit(event)}>
        <input id="name" onChange={e => setNameInput(e.target.value.trim())} required placeholder="What is your name .." /><br />
        <input id="room" onChange={e => setRoom(e.target.value.trim())} placeholder="What is your room .." /><br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

Zusammenfassung

Das war's! Wir haben gemeinsam eine voll funktionsfähige Gruppenchat-Anwendung erstellt! Wie cool ist das denn? Der vollständige Code für das Projekt ist hier auf GitHub zu finden.

Was wir in diesem Artikel behandelt haben, ist lediglich ein Einblick, wie React Hooks Ihre Produktivität steigern und Ihnen helfen können, leistungsstarke Anwendungen mit leistungsstarken Front-End-Tools zu erstellen. Ich habe eine robustere Chat-Anwendung in diesem umfassenden Tutorial erstellt. Folgen Sie ihm, wenn Sie mit React Hooks noch weiter kommen möchten.

Jetzt, da Sie praktische Erfahrung mit React Hooks haben, nutzen Sie Ihr neu gewonnenes Wissen, um noch mehr zu üben! Hier sind einige Ideen, was Sie von nun an erstellen können

  • Eine Blogging-Plattform
  • Ihre eigene Version von Instagram
  • Ein Klon von Reddit

Haben Sie Fragen auf dem Weg? Hinterlassen Sie einen Kommentar und lassen Sie uns gemeinsam großartige Dinge erschaffen.