Springe zum Inhalt →

Coding

HowTo: Erstellen von kollaborativen Echtzeit-Aufgabenlisten in React

Nachdem nun Wunderlist ja leider Geschichte ist 😫, könnte es Sinn machen, dass wir heute mal lernen können, wie man sich diesen - https://community-tasks.now.sh/ - einfachen und kollaborativen Echtzeit-Tasklistendienst selbst erstellt.😮
Benutzer können damit eine neue Liste erstellen und diese mit Freunden / Kollegen teilen, um die Liste gemeinsam zu bearbeiten und vervollständigen.

Wir werden Functional React im Frontend und Supabase als Datenbank- und Echtzeit-Engine verwenden. (Was ist Supabase?)🤔

Wer die folgende Lektüre überspringen  möchte, findet den Quellcode der Anwendung hier: https://github.com/smartDevel/community-tasks

Anderenfalls steigen wir nun in die Details ein 👀🧠

1) Projektbasis erstellen

Dafür nutzen wir create-react-app

npx create-react-app community-tasks

Nachdem die initiale Struktur erstellt wurde, werden wir unser Projekt etwas (re-) strukturieren, so dass es im Code-Editor in etwa so aussieht:

VSCode Files
VSCode Filestruktur

index.js wird unser Einstiegspunkt sein, an dem wir neue Listen erstellen, TodoList.js wird die Liste sein, die wir erstellen, und wir werden alle unsere Daten aus Store.js abrufen.

Dann müssen wir noch folgende Abhängigkeiten in unsere package.json eintragen:

package.json abhängigkeiten
package.json abhängigkeiten

und schließlich das ganze noch via npm installinstallieren.🏋️‍♀️

2) index.js

Wir fügen unserem Basis-Router die Render-Funktion hinzu:

import { render } from 'react-dom'

render(
  <div className="App">
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        {/* weitere Routen ab hier */}
      </Switch>
    </Router>
  </div>,
  document.body
)

Danach bereiten wir unsere main-Komponente vor:

const newList = async (history) => {
  const list = await createList(uuidv4());
  history.push(`/?uuid=${list.uuid}`);
};
const Home = (props) => {
  const history = useHistory();
  const uuid = queryString.parse(props.location.search).uuid;
  //GIF-Animation auf wordpress für Startscreen-Bild
  const imgStartscreen = "https://ways4eu.files.wordpress.com/2020/06/startscreengifanimationv04.gif";
  const lnkGithub = "https://github.com/smartDevel/community-tasks";
  const lnkTutorial =
    "https://dev.to/awalias/howto-build-collaborative-realtime-task-lists-in-react-4k52";
  const lnkDatabase = "https://supabase.io";

  if (uuid) return TodoList(uuid);
  else {
    return (
      <div className="container">
        <div className="section">
          <h1>Kollaborative Aufgabenlisten</h1>
          <small>
            Powered by <a href={lnkDatabase}>Supabase</a>
          </small>
        </div>
        <div className="section">
          <button
            className="bNew"
            onClick={() => {
              newList(history);
            }}
          >
            Neue Aufgabenliste
          </button>
        </div>
        <div className="section build">
          <h3>
            HowTo:
            <br />
            <a href={lnkTutorial}>Tutorial</a> | <a href={lnkGithub}>Github</a>
          </h3>
          {/* *** Alternative mit input-Feld *** */}
          <input
            type="image"
            src={imgStartscreen}
            onClick={() => {
              newList(history);
            }}
            alt="Neue Aufgabenliste"
          />
          {/* *** Alternative mit Button ***
          <button 
           onClick={() => {
            newList(history)
          }}>
            <img
              className="build-img"
              src={imgStartscreen}
              alt="learn how to build this"
            />
          </button>
          */}
          {/* *** Alternative mit Hyperlink ***
          <a onClick={() => {
              newList(history)
            }}>
            <img
              className="build-img"
              src={imgStartscreen}
              alt="learn how to build this"
            />

          </a> */}

        </div>
      </div>
    );
  }
};

Der wichtigste Teil hierbei ist, dass wir beim Klicken auf die Schaltfläche "Liste erstellen" eine Liste createList(uuidv4()) mit einer zufällig generierten UUID erstellen und diese dann mit useHistory () und history.push (...) als Abfrageparameter an die aktuelle URL anhängen. Wir tun dies, damit der Benutzer die URL aus der URL-Leiste kopieren und teilen kann.

Auch wenn ein neuer Benutzer einen Link mit uuid erhält - die Anwendung weiß, dass sie die spezifische Aufgabenliste aus der Datenbank mit der angegebenen UUID nachschlagen muss, dies ist hier zu sehen:

const uuid = queryString.parse(props.location.search).uuid;
if (uuid) return TodoList(uuid);

3) Store.js

Jetzt schauen wir uns an, wie wir die Daten in Echtzeit speichern, abrufen und überwachen, damit den adressierten Nutzern die jeweils aktuelle Liste mit allen neuen und bereits abgeschlossenen Aufgaben angezeigt wird, ohne dass diese die Seite erneut aktualisieren müssen.

import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
  process.env.REACT_APP_SUPABASE_URL,
  process.env.REACT_APP_SUPABASE_KEY
)
Wir benötigen eine .env-Datei im Root- Verzeichnis, in der wir folgende Variablen speichern:
REACT_APP_SUPABASE_URL=<meine-url>
REACT_APP_SUPABASE_KEY=<mein-key>
Um die individuellen Supabase-Anmeldeinformationen abzurufen, rufen wir app.supabase.io auf, erstellen eine neue Organisation und ein neues Projekt und navigieren zur API-Seite, auf der die benötigten Informationen zu finden sind:
API-Ansicht supabase
API-Ansicht in supabase-Konfigurationsseite

In der Supabase Konfiguration gehen wir anschließend zur Registerkarte SQL, auf der wir unsere beiden Tabellen Listen (lists) und Aufgaben (tasks) mit dem integrierten SQL-Interpreter erstellen. Dort werden wir folgende SQL-Befehle zum Erstellen der Tabellen eintragen und ausführen lassen:

CREATE TABLE lists (
  uuid text,
  id bigserial PRIMARY KEY,
  inserted_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL,
  updated_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL
);

CREATE TABLE tasks (
  task_text text NOT NULL,
  complete boolean DEFAULT false,
  id bigserial PRIMARY KEY,
  list_id bigint REFERENCES lists NOT NULL,
  inserted_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL,
  updated_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL
);

Nun können wir damit in Store.js die createList-Methode erstellen, die wir aus index.js aufrufen:

export const createList = async (uuid) => {
  try {
    let { body } = await supabase.from('lists').insert([{ uuid }])
    return body[0]
  } catch (error) {
    console.log('error', error)
  }
}

Hier ist der Rest des Codes zu Store.js abrufbar. Die anderen wichtigen Punkte in diesem Code sind

        supabase
          .from(`tasks:list_id=eq.${list.id}`)
          .on('INSERT', (payload) => handleNewTask(payload.new))
          .on('UPDATE', (payload) => handleNewTask(payload.new))
          .subscribe()

und wie wir den State mit useState () und useEffect verwalten.Das ist zunächst nicht ganz einfach zu verstehen. Sehr hilfreich ist es daher unbedingt die Erläuterung zum  Effekt-Hook durchzulesen, um zu verstehen, wie das alles zusammenpasst.

4) TodoList.js

Für die TodoList-Komponente importieren wir zunächst aus dem Store:

import { useStore, addTask, updateTask } from './Store'

und dann können wir diese  wie übliche State-Variablen verwenden:

export const TodoList = (uuid) => {
  const [newTaskText, setNewTaskText] = useState('')
  const { tasks, setTasks, list } = useStore({ uuid })

  return (
    <div className="container">
      <Link to="/">Zurück/Neustart</Link>
      <h1 className="section">✍🏼🧾✨Community Aufgabenliste✨🔜 ✔️</h1>
      <div className="section">
        <label>Aufruf-Link zum Bearbeiten und Anzeigen: </label>
        <input className="inputField" type="text" readOnly value={window.location.href} />
      </div>
      <div className={'field-row section'}>
        <form
          onSubmit={(e) => {
            e.preventDefault()
            setNewTaskText('')
          }}
        >
          <label>Neue Aufgabe hier eintragen und hinzufügen: </label>
          <input
            id="newtask"
            type="text"
            value={newTaskText}
            onChange={(e) => setNewTaskText(e.target.value)}
          />
          <button type="submit" onClick={() => (newTaskText ? addTask(newTaskText, list.id) : '')}>
            Aufgabe hinzufügen
          </button>
        </form>
      </div>
      <div className="section">
        {tasks
          ? tasks.map((task) => {
              return (
                <div key={task.id} className={'field-row'}>
                  <input
                    checked={task.complete ? true : ''}
                    onChange={(e) => {
                      tasks.find((t, i) => {
                        if (t.id === task.id) {
                          tasks[i].complete = !task.complete
                          return true
                        } else {
                          return false
                        }
                      })
                      setTasks([...tasks])
                      updateTask(task.id, { complete: e.target.checked })
                    }}
                    type="checkbox"
                    id={`task-${task.id}`}
                  ></input>
                  <label htmlFor={`task-${task.id}`}>
                    {task.complete ? <del>{task.task_text}</del> : task.task_text}
                  </label>
                </div>
              )
            })
          : ''}
      </div>
      <div className="section">
        <small>
          Achtung: Bitte keine sensiblen Informationen hier eintragen, da die Liste öffentlich aufrufbar und einsehbar ist !
        </small>
      </div>
    </div>
  )
}

Wenn wir alles soweit zum Laufen gebracht haben, sollten wir nun in der Lage sein, npm run start auszuführen und im Browser die URL localhost: 3000 aufzurufen, um die Anwendung in Action zu sehen.

Der komplette Source-Code ist in gitHub frei verfügbar.
Haftungsausschluss/ Wichtiger Hinweis 🚩⚡:
Diese Demo wird ohne Benutzerauthentifizierung geliefert. Der Zugriff auf die Liste eines anderen Benutzers ist ohne Kenntnis der uuid zwar nicht ganz einfach aber trotzdem grundsätzlich möglich. Daher ist davon auszugehen, dass alles, was in die Aufgabenlisten eingetragen wird, öffentlich verfügbare Informationen sind.
Hier geht's weiter

Mounting von React Lifecycle-Methoden

Es sind drei Kategorien von Lifecycle-Methoden zu unterscheiden:

  • Mounting - Bereitstellen,
  • Updating - Aktualisieren
  • Unmounting - Aufheben der Bereitstellung.

In diesem Artikel geht es um die erste Kategorie: Bereitstellen (Mounting) von Lebenszyklusmethoden.

Eine Komponente wird beim ersten Rendern "gemountet".
Dies ist der Fall, wenn Mounting Lifecycle Methods (Mounting-Lebenszyklus) aufgerufen werden.

Es gibt drei Methoden für den Mounting-Lebenszyklus:

  1. componentWillMount
  2. render
  3. componentDidMount

Wenn eine Komponente bereitgestellt wird (mounts), werden diese drei Methoden automatisch der Reihe nach aufgerufen.

1. componentWillMount

Die erste Methode für den Mounting-Lebenszyklus heißt componentWillMount.

Wenn eine Komponente zum ersten Mal gerendert wird, wird componentWillMount direkt vor dem Rendern aufgerufen.

import React from 'react';import ReactDOM from 'react-dom';

export class Example extends React.Component {

componentWillMount() {

alert('component is about to mount!');

}

render() {

return <h1>Hello world</h1>;

}

}

ReactDOM.render(

<Example />,

document.getElementById('app')

);

setTimeout(() => {

ReactDOM.render(

<Example />,

document.getElementById('app')

);

}, 2000);

Schauen Sie sich das obenstehende Beispiel an.

Erläuterung :
In den Zeilen

ReactDOM.render(

<Example />,

document.getElementById('app')

);

wird <Example /> zum ersten Mal gerendert. Die Mounting-Periode von <Example/> beginnt.
<Example/> ruft die erste Mounting-Lifecycle-Methode, componentWillMount, auf.
componentWillMount wird ausgeführt und eine Warnung wird auf dem Bildschirm angezeigt:

  componentWillMount() {

alert('component is about to mount!');

}

Nach dem Abschluss von componentWillMount ruft <Example /> die zweite Methode für den Mounting-Lebenszyklus auf: render.

  render() {

return <h1>Hello world</h1>;

}

<h1> Hallo Welt </ h1> erscheint auf dem Bildschirm

Zwei Sekunden später wird <Example /> erneut gerendert.

setTimeout(() => {

ReactDOM.render(

<Example />,

document.getElementById('app')

);

}, 2000);

componentWillMount wird dieses Mal jedoch NICHT aufgerufen, da das Mounten von Lifecycle-Ereignissen nur beim ersten Rendern einer Komponente ausgeführt wird.

In componentWillMount kann this.setState aufgerufen werden!

Nachstehendes Coding ist ein Beispiel für this.setState in componentWillMount. Wie wird hier <MyExample /> <h1> Hallo Welt </ h1> gerendert ?

import React from 'react';


import ReactDOM from 'react-dom';

export class MyExample extends React.Component {
constructor(props) {
super(props);

this.state = { text: '' };
}

componentWillMount() {
this.setState({ text: 'Hello world' });
}

render() {
return <h1>{this.state.text}</h1>;
}
}

ReactDOM.render(
<MyExample />,
document.getElementById('app')
);

2. Render

Rendern ist eine Lebenszyklusmethode !

Wie fügt sich das rendern in den Lebenszyklus ein ?
Immer wenn eine Komponente bereitgestellt wird, wird componentWillMount zuerst aufgerufen, gefolgt von render, gefolgt von componentDidMount.

Das Rendern gehört zu zwei Kategorien:
Mounten von Lebenszyklusmethoden und Aktualisieren von Lebenszyklusmethoden.

3. componentDidMount

Die endgültige Methode für den Lebenszyklus der Bereitstellung heißt componentDidMount.

Wenn eine Komponente zum ersten Mal gerendert wird, wird componentDidMount direkt aufgerufen, nachdem der HTML-Code aus dem Rendern vollständig geladen wurde. Nachstehend ein kleines Beispiel für componentDidMount.

import React from 'react';

export class Example extends React.Component {

  componentDidMount() {

    alert('component just finished mounting!');

  }

  render() {

    return <h1>Hello world</h1>;

  }

}

componentDidMount wird sehr viel genutzt !

Wenn die React-App AJAX verwendet, um initial Daten von einer API abzurufen, ist componentDidMount der Ort, an dem dieser AJAX-Aufruf ausgeführt wird. Im Allgemeinen ist componentDidMount ein guter Ort, um eine React-App mit externen Anwendungen wie Web-APIs oder JavaScript-Frameworks zu verbinden. componentDidMount ist auch normalerweise der Ort, an dem Timer mit setTimeout oder setInterval festgelegt werden.

Wenn das noch zu wenig konkret klingt, keine Sorge, wir werden die Lebenszyklusmethoden und ihre Praxis-Anwendung  in weiteren Artikeln noch näher betrachten.

Hier geht's weiter

React Hooks besser anwenden

In diesem Artikel werde ich versuchen, die möglichen Best Practices vorzustellen, die mit React Hooks machbar sind , um React Hooks als Entwickler in zukünftigen Projekten besser verwenden zu können.

Hierzu ist ein Grundverständnis in reactJs erforderlich.

Warum sollten ReactHooks verwendet werden ?

React ab V16.8 enthält Hooks und ermöglicht das Verwalten von Status- und anderen React-Funktionen, ohne das eine Klasse erforderlich ist.

Können wir nicht einfach weiterhin Klassen verwenden? Ja, geht zweifelsohne, allerdings sollte es wohlüberlegt sein,denn mit Functional Components geht einiges besser:

  • Einfache Trennung von Container- und Präsentationskomponenten
  • Besser lesbare und testbare Komponenten.
  • weniger Code erforderlich.
  • Performance-Boost 🚀

React bietet Entwicklern bereits vordefinierte, integrierte Hooks wie useState, useEffect, useContext ... und bietet zusätzlich die Möglichkeit auch eigene Hooks zu erstellen (dazu später mehr in einem separaten Artikel) .

Zunächst einige Beispiele, die sich auf useState und useEffect Hooks konzentrieren. Die gleichen Regeln sind auch auf die anderen Hooks anwendbar.

import React, { useState } from 'react'; // Aufruf des useState Hook

const EinfacheKomponente1 = () => { // Hooks in funktionalen Komponenten 
  const [count, setCount] = useState(0) //Initialisierung
  return (
    <div>
      <p>Klick Anzahl : {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Klick mich     
      </button>
    </div>
  )
}

ReactDOM.render(<EinfacheKomponente1 />, document.getElementById('app'))

Der obige Code zeigt die Verwendung von useState Hooks. Im Beispiel handelt es sich um eine einfache Komponente, mit der der Benutzer eine Zahl (Counter) erhöhen kann, wenn das Klickereignis ausgelöst wird. Ohne Verwendung von Hooks könnten wir etwas Ähnliches schreiben (mittels class-based-components, siehe den folgenden Code), um die Werte zu initialisieren dient dann der Konstruktor innerhalb der Klasse:

this.state = {
   count:0 
}

Wenn der useState-Hook verwendet wird, ist die Initialisierung in functional Components wie folgt machbar:

const [count, setCount] = useState(0);

Das Klickereignis hat die useState-Funktion ausgelöst, die den Counter erhöht.

Das gleiche Problem könnte gelöst werden, wenn wir Klassen verwenden, allerdings dann mit mehr Code.
Wenn wir uns vorstellen, dass die Komplexität der zuverwaltenden states immer größer wird, wird es dann schnell unübersichtlich 🤷‍♂️

Die Verwendung von Hooks kann allerdings auch zu Komplexität führen.

Ziel dieses Artikels ist es, zu zeigen, dass mit wenigen Schritten komplexe Apps erstellt und deren Komplexität mithilfe von Hooks😉 verwaltet werden können.

Nun wollen wir sehen, wie wir dieses Ziel erreichen können.

Basierend auf der React-Doku zu Hooks sind nur einige wenige Regeln zu befolgen, um mit dem Schreiben von Hooks-basierten Apps beginnen zu können.

1. Wo Hooks verwenden und in welcher Reihenfolge ?

Zum Wo sind folgende Regeln wichtig. Hooks werden niemals aufgerufen in:

✔ Klassenkomponenten
✔ Event Handler 
✔ Loops
✔ in Funktionen, die an useMemo, useReducer, or useEffect übergeben werden
✔ in nicht-React Funktionen
if (true) { // der useEffect hook wird in Bedingung aufgerufen 😦
  useEffect(myFunction() => {
    console.log('hier rendern')
  });
}

Jetzt haben wir gelernt, wo wir Hooks nicht aufrufen dürfen.
Die erste Regel besteht darin, die Aufruf-Reihenfolge zu respektieren und Hooks stets in der obersten Ebene der Funktionskomponente aufzurufen. Im Klartext bedeutet dies: React muss wissen, dass Hooks immer in derselben Reihenfolge aufgerufen werden und nicht von Bedingungen oder Schleifen beim Rendern abhängen.

Die Verwendung von React-Hooks unter Beachtung der React-Regeln sollte dann zB ungefähr so aussehen:

const einfacheKomponente2 = () => { 
  const [count, setCount] = useState(0) ✔
  const [name, setCount] = useState('') ✔
  useEffect(() => { ✔
    document.title = `Du hast mich ${count} mal geklickt.`;
  });
}

Für die diejenigen, die mehr wissen möchten, zB warum die Reihenfolge so wichtig ist und was sich hinter den Kulissen so tut, und auch mehr über verknüpfte Listen und Datenstrukturen wissen möchten,gibt es hier eine gute und weiterführende Erläuterung.

2.  React-Hooks nur innerhalb von React-Functional-Components verwenden

React- Funktionskomponenten und Javascript-Funktionen sind nicht dasselbe. Das Aufrufen von Hooks innerhalb einer js-Funktion ist nicht möglich. Funktionale React-Komponenten akzeptieren props als Argumente und geben JSX zurück, selbst wenn sie denselben body und dieselbe Struktur wie eine einfache Javascript-Funktion haben. 🤷‍♀️

import { useState } = "react";

const sagHallo(name) => {
  const [name, setName] = useState("Franz");
  return console.log(`Hallo ${name}`);
}
document.getElementById("root").innerHTML = sagHallo;

Der useState Hook ist hier unbrauchbar, da es sich nicht um eine React- Funktions-Komponente handelt.

3. React CustomHooks - benutzerdefinierte Hooks

Benutzerdefinierte Hooks sind eine neue Methode, womit die Flexibilität geboten wird, Logik zu teilen, was in React-Komponenten zuvor nicht möglich war. Es kann verwendet werden, um viele Anwendungsfälle wie das Abrufen von Daten, komplexe Animationen, deklarative Abonnements, Timer usw. abzudecken.

Benutzerdefinierte Hooks bedeuten weniger Code. und weniger sich wiederholende Blöcke.

Stellen wir uns vor, wir verwenden eine Komponente, die einen sehr komplexen großen State verwendet. Der gleiche State wird möglicherweise von anderen Komponenten gemeinsam genutzt. Wenn der State größer wird, ist es sehr schwierig, seine logische Komplexität zu verwalten.

Die Verwendung vieler setState in der Funktionskomponente macht das Aktualisieren des State nicht zentral und einfacher. Daher kommt auch die Idee der -Reducer-  😉, das Erstellen eines benutzerdefinierten useReducer-Hooks löst dieses Problem für viele andere Komponenten.

Erstellen wir einen einfachen benutzerdefinierten Hook, der einen Titel anzeigt. Unser Hook befindet sich in einer separaten Datei und sieht folgendermaßen aus:

const useTitle= (title) => {
  useEffect(() => {
    document.title = title;
  }, [title])
}

Wie wir sehen können, ist ein benutzerdefinierter Hook eine Funktion, die mit use beginnt und einen anderen Hook im Inneren verwenden kann 😁 und natürlich kann dieser Hook überall angewendet werden !!  🚀

import React, { Component, useState } from 'react';
 import useTitle from './myHooks/usetitle'; // benutzerdefinierter Hook

 const Counter() => {
    const [count, setCount] = useState(0);
    const incrementCount = () => setCount(count + 1);
    useTitle (`You clicked ${count} times`);

    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={incrementCount}>Click me</button>
      </div>
    )
  }

  export default Counter;

Vorstellbar ist zB  einen useLocalStorageHook zu erstellen, um den lokalen Speicher zu verwalten, useValidDataHook zu verwenden, um ein Formular zu validieren ... und vieles mehr!
Damit wäre ein wesentlich sauberer und zentralisierter Code möglich 😉.

4. Code-Linter verwenden !

Um lästige Konflikte beim Coding zu vermeiden, sollte ein esLint-Plugin verwendet werden, um sicherzustellen, dass alle oben genannten Regeln angewendet werden. Insbesondere wenn im Team gearbeitet wird.

create-react-app (CRA) enthält bereits das Plugin, anderenfalls kann es wie folgt installiert werden:

npm install eslint-plugin-react-hooks --save-dev

Folgende Konfiguration sollte in der esLint-Konfigurationsdatei enthalten sein, um die o.a. Regeln im Linter zu aktivieren:

// ESLint Konfiguration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Überprüft Hook-Regeln
    "react-hooks/exhaustive-deps": "warn" // Überprüft effects-deps.
  }
}

Zusammenfassung

Ich habe in diesem Artikel versucht, die wichtigsten Regeln für Entwickler aufzulisten, die bereit sind, im nächsten Projekt React-Hooks zu verwenden. Für die Verwendung von Hooks sind die folgenden Schritte zu validieren:

Einfachheit ✔
Organisation: In dieser Reihenfolge: zuerst berechnen , dann State instantiieren, Side-Effects beachten, Ereignishandler einrichten, schließlich rendern (). ✔
Code Type-Check, weitere Infos dazu
Styleguide verwenden, um Code-Einheitlichkeit und Integrität zu gewährleisten. ✔

Hier geht's weiter

Container Komponenten in React

Ein React-Pattern, das im Coding von React Frontends Berücksichtigung finden sollte, ist das Container-Komponenten-Pattern.

Die Idee ist einfach:

Ein Container ruft Daten ab und rendert dann die entsprechende Unterkomponente. So simpel.

"Entsprechend" bedeutet eine Komponente mit demselben Namensbestandteil:

GuineaPigsContainer => GuineaPigs
AnimalsContainer => Animals
EventListContainer => EventList

Konzept verstanden ?

Warum Container?

Angenommen, du hast eine Komponente, die Kommentare anzeigt. Da du noch nichts vom Container-Komponenten-Pattern gehört hast, platzierst du deinen Code so, dass  alles an einem Ort ist:

class CommentList extends React.Component {
  this.state = { comments: [] };

  componentDidMount() {
    fetchSomeComments(comments =>
      this.setState({ comments: comments }));
  }  render() {
    return (
      <ul>
        {this.state.comments.map(c => (
          <li>{c.body}—{c.author}</li>
        ))}
      </ul>
    );
  }
}

Deine Komponente ist sowohl für das Abrufen als auch für das Präsentieren von Daten verantwortlich. Daran ist nichts „Falsches“, aber einige Vorteile von React werden damit nicht nutzbar, zB:

Wiederverwendbarkeit

CommentList kann nur unter genau den gleichen Umständen wiederverwendet werden.

Datenstruktur

Die Markup-Komponenten sollten die Erwartungen an die benötigten Daten angeben. PropTypes sind dafür optimal.
Unsere Komponente hat eine Erwartung an die Datenstruktur, kann diese jedoch nicht ausdrücken. Wenn sich der json-Endpunkt ändert, wird der Datenfluß zur Komponente unterbrochen (silent).

Noch einmal. Diesmal mit einem Container

Lass uns zunächst den Datenabruf in eine Containerkomponente verlagern:

class CommentListContainer extends React.Component {
  state = { comments: [] };  componentDidMount() {
    fetchSomeComments(comments =>
      this.setState({ comments: comments }));
  }  render() {
    return <CommentList comments={this.state.comments} />;
  } 
}

Lass uns nun CommentList überarbeiten, um comments als prop zu verwenden:

const CommentList = props =>
  <ul>
    {props.comments.map(c => (
      <li>{c.body}—{c.author}</li>
    ))}
  </ul>

Also, was haben wir damit erreicht ?

Tatsächlich eine ganze Menge....

  • Wir haben das Abrufen und Rendern von Daten getrennt.
  • Wir haben unsere CommentList-Komponente wiederverwendbar gemacht.
  • Wir haben CommentList die Möglichkeit gegeben, PropTypes festzulegen und damit ggf. auch im Fehlerfall "laut" zu scheitern.

Containerkomponenten können die Struktur und Lesbarkeit von React-Code erheblich verbessern und sie erleichtern das Lesen des Komponenten-Codes.

Probiere es aus !

Hier geht's weiter

[Gesamt: 0   Durchschnitt:  0/5]
%d Bloggern gefällt das: