Ein Einblick in React Hooks

03.03.2022

Bisher konnten in React Komponenten auf zwei Arten entwickelt werden: Als Klasse oder als Funktion. Sobald in einer Komponente ein interner State oder Lifecycle-Methoden verwendet wurden, musste sie als Klasse umgesetzt werden.

Ein neues Feature, das mit der React Version 16.8. Einzug erhielt, sind Hooks. Durch Hooks ist es möglich in funktionsbasierten Komponenten State Änderungen, Seiteneffekte oder andere Features zu nutzen. Sie bringen dadurch keine Breaking Changes mit, sondern sie sind eine weitere Variante zur Entwicklung von Komponenten.

Welche Hooks gibt es?

React liefert insgesamt 10 eigene Hooks. Diese werden laut der offiziellen Dokumentation in 3 Basic und 7 Additional Hooks unterteilt. In den meisten Fällen werden die 3 Basic Hooks useState()useEffect() und useContext() am meisten verwendet.
Additional Hooks sind insbesondere zur Abdeckung von Corner Cases und/oder zur Optimierung der Anwendung angedacht. Neben den 10 eigenen Hooks von React ist es außerdem möglich, eigene Custom Hooks zu schreiben.

Welche Vorteile bringen Hooks?

Durch Hooks ist es möglich, State-behaftete Logik aus einer Komponenten zu extrahieren, sodass diese unabhängig getestet und wiederverwendet werden können. Einfach gesagt: Hooks erlauben es, das Rendern von UI-Komponenten von der Business-Logik zu trennen. Auch ein Vorteil von Hooks ist die Vermeidung von Higher order components. Bei High order components handelt es sich meistens um Wrapper Klassen, die dafür genutzt werden, um einen State zwischen Komponenten zu teilen. Dies führt oft zur sogenannten „Wrapper Hell“ was den Code unnötig verschachtelt und komplex macht. Durch Hooks werden States wiederverwendbar – ohne die hierarchische Struktur der Komponenten verändern zu müssen. Ein weiterer Vorteil ist der Cleanere Code. Beispielweise wird das this-property obsolet. Funktionalitäten des Codes, die eigentlich zusammengehören, werden an unterschiedlichen Stellen behandelt, z.B. bei Lifecycle-Methoden. Dies wird durch Hooks ebenfalls verbessert. Siehe hierzu die Codebeispiele untern. Es ist generell einfach möglich Code sauberer zu strukturieren und Nebenläufigkeiten (wie async-requests) abzubilden.

Welche Regeln gibt es beim Verwenden von Hooks?

Hooks dürfen nur auf Top-Level Ebene der React Komponente verwendet werden. Da es möglich ist, die selbe Hook-Methode mehrfach zu verwenden, muss sichergestellt werden, dass bei jedem Rendering der Komponente immer die gleiche Reihenfolge der Methoden ausgeführt wird. Das ist wichtig, damit der State korrekt erhalten bleibt. Eine weitere Regel besagt, dass Hooks nur innerhalb von React-Funktionen und in Custom Hooks aufgerufen werden dürfen. Jedoch nicht in normalen JavaScript Funktionen. Um dies zu gewährleisten gibt es ein extra Linter-Plugin, das verwendet werden sollte. Ein genaueren Einblick (und auch zur config des Plugins) gibt es in der offiziellen Doku.

Wann sollten Hooks verwendet werden?

Es wird nicht empfohlen, bestehende React Klassen zwingend durch Hooks zu ersetzen. Hooks sollen als Ergänzung dienen, nicht als Ablöse (zumindest auf absehbare Zeit). Für Performance Optimierungen kann es jedoch sinnvoll sein, bestimmte Teile des Codes in Hooks auszulagern. Beim Entwickeln neuer Komponenten oder eines neuen Projekts sollten jedoch definitiv Hooks verwendet werden. Im Gegensatz zu Klassen gibt es insgesamt weniger Code, der auch deutlich flexibler und wartbarer ist. Auch für Neueinsteigende ist die Hürde dadurch deutlich geringer.

Hooks Beispiele

Zum besseren Verständnis werden die 3 Basic Hooks (useState()useEffect() und useContext()) in einem Minimalbeispiel dargestellt. Zum Vergleich wird die gleiche Funktion als Klasse und als Hook gegenübergestellt.

useState()

useState() wird am häufigsten verwendet. Damit wird eine Veränderung des bestehenden State vorgenommen. Wenn sich die Werte in einem State verändert, ist dies ein Signal, um das UI neu zu rendern.

Klasse Hook
class Counter extends React.Component {
 
  constructor(props){
   super(props);
   this.state = {
     count: 0
   };
  }
 
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          {this.state.count}
        </button>
      </div>
    );
  }
}
function Counter() {
 
  const [ count, setCount ] = useState(0); // 0 ist der initiale State
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>
    </div>
  );
}
  • Wir benötigen eine Klasse, um „Component“ zu extenden
  • Es wird ein extra State Objekt benötigt
  • Es wird ein Constructor benötigt, um den State zu initialisieren
  • this-paramter wird überall benötigt, um auf den State zuzugreifen
  • Keine Klasse, kein Constructor, kein this-thisparamter, kein State Objekt
  • Count ist der Wert im State
  • setCount ist die Setter-Methode für die Variable Count

useEffect()

Der useEffect()Hook ist für die Ausführung von Side Effects vorgesehen. Damit sind Seiteneffekte gemeint, wie das Laden von Daten via API, die Manipulation des DOM-Baums, Event-Handler, Console-Ausgabe, etc..

Der Aufbau der Syntax des useEffect()-Hooks ist dabei wie folgt:

useEffect(callback, dependencyArray);
  • Der Callback ist der Seiteneffekt, der ausgeführt werden soll.
  • Das Dependency Array kann dabei unterschiedlich aussehen:
    • (kein Wert) = Immer ausführen
    • [] = Einmal bei Komponenten-Start ausführen
    • [a, b] = Immer wenn sich einer der Werte (a oder b) geändert hat ausführen
    • Außerdem wichtig: alle Variablen, die innerhalb des effects verwendet werden, müssen in den dependencies aufgelistet werden. Für genauere Erklärung siehe diesen Artikel.

Mit einem useEffect-Hook lassen sich beispielsweise die Lifecycle Methoden componentDidMount()componentDidUpdate() und componentWillUnmount() abbilden.

Klasse Hook
const defaultText = 'Einblick in React Hooks';
 
class Counter extends React.Component {
  state = {
    count: 0,
  };
 
  componentDidMount() {
    document.title = `Button Click Nr.: ${this.state.count}`;
  }
 
  componentWillUnmount() {
    document.title = defaultText;
  }
 
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          {this.state.count}
        </button>
      </div>
    );
  }
}
const defaultText = 'Einblick in React Hooks';
 
function Counter() {
  const [ count, setCount ] = useState(0);
   
  useEffect(() => {
    document.title = `Button Click Nr.: ${count}`; // 1
    return () => {
      document.title = defaulttext; // 2
    }
  }, [count]); // 3
   
  return (
    <div>
      <button onClick={() => setCount(count+ 1)}>
        {count}
      </button>
    </div>
  );
}
  • Für jeden Life-Cycle wird eine eigene Methode implementiert componentDidMount()componentDidUpdate() und componentWillUnmount()
  1. Was soll passieren, wenn die useEffect() Methode gtriggert wird.
  2. Als Return wird hinterlegt, welche Funktion vor dem Update zurückgegeben bzw. ausgeführt werden soll.
  3. Als Dependency wird festgelegt, welcher Wert sich ändern muss, damit useEffect() ausgeführt wird. In diesem Fall „count“.

Für einen tieferen Einblick in useEffects() lohnt sich ein Blick in diesen Artikel.

useContext()

Die Context-API von React ermöglicht es, Daten über die gesamten Komponenten-Baum zu teilen.

Mit useContext() ist es einfach, Daten aus einem Context-Provider zu konsumieren. Zusätzliche Provider-Komponenten mit einer Function as a Child werden dadurch nicht mehr benötigt.
Der useContext()-Hook besitzt einen erzeugten Context und gibt dann den Wert des Providers zurück. Wird der Wert des Contexts im Provider geändert, löst der useContext() Hook ein Re-rendering mit den neuen Daten aus dem Provider aus.

Klasse Hook
function UserConsumer({user}) {
  return (
    <UserContext.Consumer>
      {context => {
        return user(context)
      }}
    </UserContext.Consumer>
  )
}
class UserThing extends React.Component {
  render() {
    return (
      <UserConsumer>
        {({state, dispatch}) => (
          <div>
            <div>{state.user.name}</div>
          </div>
        )}
      </CountConsumer>
    )
  }
}

 

const UserContext = React.createContext();
  
function App () {
  var user = {
    name: 'Bob',
    age: '30'
  }
  return (
    <UserContext.Provider value={user}>
      <div>
        <NameDisplay />
      </div>
    </UserContext.Provider>
  );
}
  
function NameDisplay() {
  const user = useContext(UserContext);
  return (
    <div>My name is: {user.name}</div>
  );
}

 

  • Wrapper Komponente nötig
  • Consumer-Komponente nötig
  • Context kann aus UserContext gelesen und verwendet werden
  • Keine UserContext Consumer-Komponente nötig
  • Deutlich übersichtlicher Code, da die Verschachtelungstiefe flacher ist.
  • Dieser Weg ist optional, es kann auch weiterhin mit Consumer-Komponente gearbeitet werden

 

Eine Übersicht über alle weiteren Hooks bietet die offizielle Doku von React.

 

Mehr über Frontend Entwicklung erfahren

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*