Wie Komposition über Vererbung die Komponentenentwicklung neu gestaltet hat
Min-jun Kim
Dev Intern · Leapcell

Einleitung
Seit Jahren haben objektorientierte Programmierprinzipien (OOP), insbesondere die Vererbung, die Art und Weise, wie wir unsere Frontend-Komponenten strukturierten, stark beeinflusst. Entwickler fanden sich oft darin wieder, Klassenhierarchien zu erstellen, Basiskomponenten zu erweitern und komplexe this-Kontexte zu navigieren, um Logik wiederzuverwenden. Obwohl dieser Ansatz zunächst durch seine vermeintliche Ordnungswidrigkeitreizte, führte er häufig zu Problemen wie "Wrapper Hell", Prop Drilling und brüchigen Komponentenstrukturen, bei denen eine Änderung in einer übergeordneten Klasse unerwartet eine untergeordnete brechen konnte. Dieses starre Vererbungsmodell schränkte die Flexibilität ein und erschwerte die Extraktion und gemeinsame Nutzung von zustandsbehafteter Logik über verschiedene Komponenten hinweg. Hier kommt der Paradigmenwechsel: Komposition über Vererbung. Dieses grundlegende Prinzip, das seit langem in der Softwaretechnik gefeiert wird, wurde mit dem Aufkommen von React Hooks und Vue Composition API in der Frontend-Welt dramatisch neu belebt. Diese bahnbrechenden Funktionen haben nicht nur das Zustandsmanagement und die Handhabung von Nebeneffekten vereinfacht, sondern auch die Art und Weise, wie wir über wiederverwendbare Komponentenlogik denken und diese schreiben, grundlegend verändert und den Weg für wartbarere und skalierbarere Anwendungen geebnet.
Eine neue Ära der Komponentenentwicklung
Bevor wir uns mit den Besonderheiten von Hooks und der Composition API befassen, lassen Sie uns kurz einige Schlüsselkonzepte definieren, die dieser Diskussion zugrunde liegen:
- Komposition: In der Softwaretechnik bezieht sich Komposition auf den Akt der Kombination kleinerer, fokussierterer Funktionen oder Objekte, um größere, komplexere zu erstellen. Im Gegensatz zur Vererbung, bei der eine neue Klasse eine bestehende erweitert, konzentriert sich die Komposition auf "Hat-ein"-Beziehungen statt auf "Ist-ein"-Beziehungen. Dies fördert Flexibilität und Wiederverwendbarkeit.
- Kapselung: Das Bündeln von Daten (Zustand) und Methoden (Verhalten), die auf den Daten operieren, in einer einzigen Einheit. In Frontend-Komponenten bedeutet dies typischerweise, zusammengehörige Logik beisammen zu halten.
- Trennung der Belange (Separation of Concerns): Die Praxis, ein Computerprogramm in verschiedene Funktionen zu unterteilen, die sich in ihrer Funktionalität so wenig wie möglich überschneiden. Dies erleichtert das Entwerfen, Verstehen und Warten von Software.
Der Aufstieg von React Hooks
React Hooks, die in React 16.8 eingeführt wurden, revolutionierten die Art und Weise, wie Funktionskomponenten Zustände und Nebeneffekte verwalten. Vor den Hooks waren Zustand und Lebenszyklusmethoden ausschliesslich Klassenkomponenten vorbehalten, was Entwickler zwang, Funktionskomponenten in Klassen zu konvertieren, wenn Zustand oder Effekte benötigt wurden. Dies führte oft zu "HOC Hell" oder "Render Prop Hell" für die Wiederverwendung von Logik. Hooks bieten eine Möglichkeit, zustandsbehaftete Logik wiederzuverwenden, ohne Ihre Komponenten-Hierarchie zu ändern.
Im Kern ist ein Hook eine spezielle Funktion, die es Ihnen ermöglicht, von Funktionskomponenten "in" React-Funktionen "einzuhaken". Betrachten wir useState und useEffect als Paradebeispiele:
useState für die Zustandsverwaltung
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); // Initialisiert count auf 0 return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }
Hier ermöglicht useState einer Funktionskomponente, ihren eigenen Zustand zu speichern. Die Komponente erbt keine Zustandsverwaltungsfähigkeiten von einer Basisklasse; stattdessen komponiert sie diese durch Aufruf von useState.
useEffect für Nebeneffekte
import React, { useState, useEffect } from 'react'; function DocumentTitleUpdater() { const [count, setCount] = useState(0); useEffect(() => { // Dies wird nach jedem Rendern ausgeführt document.title = `Count: ${count}`; }, [count]); // Effekt nur neu ausführen, wenn sich count ändert return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button> </div> ); }
useEffect behandelt Nebeneffekte wie Datenabruf, Abonnements oder manuelle DOM-Änderungen. Anstatt Logik über componentDidMount, componentDidUpdate und componentWillUnmount zu verteilen, ermöglicht useEffect, zusammengehörige Logik basierend darauf, was sie tut, nicht wann sie ausgeführt wird, zusammenzufassen. Das Abhängigkeitsarray [count] stellt sicher, dass der Effekt nur neu ausgeführt wird, wenn sich count ändert, was Kontrolle und Leistung weiter verbessert.
Benutzerdefinierte Hooks für wiederverwendbare Logik
Die wahre Stärke von Hooks liegt in benutzerdefinierten Hooks. Das sind JavaScript-Funktionen, deren Namen mit use beginnen und die andere Hooks aufrufen können. Sie ermöglichen es Ihnen, zustandsbehaftete Logik in wiederverwendbare Funktionen zu extrahieren.
import { useState, useEffect } from 'react'; function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => { // Cleanup-Funktion window.removeEventListener('resize', handleResize); }; }, []); // Leeres Abhängigkeitsarray bedeutet, dass dieser Effekt einmal beim Mounten ausgeführt wird und beim Unmounten bereinigt wird return width; } function MyComponent() { const width = useWindowWidth(); // Fensterbreitenlogik komponieren return ( <div> <p>Window width: {width}px</p> </div> ); }
Der benutzerdefinierte Hook useWindowWidth kapselt die Logik zur Nachverfolgung der Fensterbreite. Jede Komponente kann diese Logik nun ganz einfach durch Aufruf von useWindowWidth() komponieren, ohne Vererbung oder Prop Drilling. Dies löst direkt das Problem der gemeinsamen Nutzung von nicht-visueller Logik.
Vue Composition API
Vue 3 führte die Composition API ein, eine Reihe von additiven APIs, die es uns ermöglichen, die Komponentenlogik durch importierte Funktionen zu komponieren. Ähnlich wie React Hooks bietet sie eine leistungsstarke Alternative zur Options API (die oft für ihre verstreute Logik über data, methods, computed, watch und Lifecycle Hooks kritisiert wurde). Die Composition API fördert die Gruppierung logischer Anliegen, unabhängig von ihrer Art.
setup-Funktion und reaktiver Zustand
Die setup-Funktion ist der Einstiegspunkt für die Verwendung der Composition API in einer Komponente. Sie wird ausgeführt, bevor die Komponente erstellt wird, und hier deklarieren Sie reaktive Zustände, berechnete Eigenschaften, Watcher und Lifecycle Hooks.
<template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script> import { ref, onMounted } from 'vue'; export default { setup() { const count = ref(0); // Reaktiver Zustand mit ref erstellt function increment() { count.value++; } onMounted(() => { console.log('Komponente wurde gemountet!'); }); return { count, increment, }; }, }; </script>
Hier erstellt ref eine reaktive Referenz auf einen Wert, ähnlich wie useState von React für primitive Typen. Der Lifecycle-Hook onMounted wird importiert und innerhalb von setup aufgerufen, wodurch Lifecycle-Belange nahe an der Logik gehalten werden, die sie beeinflussen.
Wiederverwendbare Logik mit Composables
Das Vue-Äquivalent zu benutzerdefinierten Hooks wird "Composables" genannt. Dies sind Funktionen, die zustandsbehaftete Logik kapseln und über Komponenten hinweg wiederverwendet werden können.
// useWindowWidth.js import { ref, onMounted, onUnmounted } from 'vue'; export function useWindowWidth() { const width = ref(window.innerWidth); const handleResize = () => { width.value = window.innerWidth; }; onMounted(() => { window.addEventListener('resize', handleResize); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); }); return { width }; }
<template> <div> <p>Window width: {{ width }}px</p> </div> </template> <script> import { useWindowWidth } from './useWindowWidth'; export default { setup() { const { width } = useWindowWidth(); // Fensterbreitenlogik komponieren return { width }; }, }; </script>
Genau wie die benutzerdefinierten Hooks von React kapselt dieses useWindowWidth-Composable effektiv die Logik zur Nachverfolgung der Fenstergrösse. Komponenten können diese Logik nun importieren und nutzen, ohne sie direkt zu implementieren oder zu erben, was zu saubereren, fokussierteren Komponenten führt.
Die Auswirkungen auf das Komponentendesign
Sowohl React Hooks als auch Vue Composition API veranschaulichen das Prinzip "Komposition über Vererbung" durch:
- Verbesserte Code-Organisation: Zusammengehörige Logik (z.B. Datenabruf und dessen Lade-/Fehlerzustände) kann innerhalb eines Hooks oder Composables zusammengefasst werden, anstatt über mehrere Lebenszyklusmethoden oder Optionen aufgeteilt zu werden.
- Verbesserte Wiederverwendbarkeit: Logik kann als einfache JavaScript-Funktionen extrahiert und geteilt werden, was es unglaublich einfach macht, zustandsbehaftete und nebeneffektreiche Logik über verschiedene Komponenten hinweg wiederzuverwenden, ohne neue Ebenen im Komponentenbaum einzuführen.
- Abgeflachte Komponenten-Hierarchien: Sie machen Higher-Order Components (HOCs) oder Render Props zur gemeinsamen Nutzung von Logik überflüssig und reduzieren so Wrapper Hell und tief verschachtelte Komponentenbäume.
- Erhöhte Lesbarkeit und Wartbarkeit: Komponenten werden leichter verständlich, da die Logik für ein bestimmtes Feature zusammengelegt ist. Das Debugging wird vereinfacht, da Sie die Logik innerhalb in sich geschlossener Einheiten nachverfolgen können.
- Flexible und mächtige Abstraktionen: Entwickler können leistungsstarke Abstraktionen erstellen, die auf die Bedürfnisse ihrer Anwendung zugeschnitten sind, und so einen domänenspezifischen Satz wiederverwendbarer Werkzeuge schaffen.
Im Wesentlichen befähigen diese APIs Entwickler, eine Komponente als eine Komposition von Verhaltensweisen zu betrachten, anstatt als eine monolithische Klasse oder eine Sammlung von unterschiedlichen Optionen.
Fazit
Der Wandel von einer stark auf Vererbung basierenden Komponentenentwicklung hin zu kompositionsbasierten Ansätzen mit React Hooks und Vue Composition API markiert eine bedeutende Weiterentwicklung in der Frontend-Entwicklung. Indem sie Entwicklern ermöglichen, zustandsbehaftete Logik als diskrete, aufrufbare Funktionen zu extrahieren, wiederzuverwenden und zu organisieren, haben diese Innovationen die Code-Organisation, Wiederverwendbarkeit und Lesbarkeit drastisch verbessert. Sie befähigen uns, robustere, skalierbarere und wartbarere Anwendungen zu erstellen, indem sie modulare, flexible Komposition gegenüber starrer, brüchiger Vererbung bevorzugen. Dieser Übergang stellt eine bewusste Bewegung hin zur Entwicklung von Komponenten dar, die einfacher, fokussierter und letztendlich leichter zu durchdenken sind, und gestaltet unsere Benutzeroberflächen grundlegend neu.