paint-brush
So implementieren Sie das State-Designmuster in JavaScript und integrieren es mit React Hooksvon@anonymouswriter_randomhandleasdad123125156135
Neue Geschichte

So implementieren Sie das State-Designmuster in JavaScript und integrieren es mit React Hooks

von anonymouswriter13m2025/03/22
Read on Terminal Reader

Zu lang; Lesen

Ich schreibe diesen Artikel, weil ich keine ähnliche Lösung wie meine gefunden habe. Meine Lösung könnte also für andere nützlich sein. Wir implementieren das State-Designmuster genau wie vom Refactoring-Guru empfohlen. Verwenden Sie das State-Muster im React-Hook.
featured image - So implementieren Sie das State-Designmuster in JavaScript und integrieren es mit React Hooks
anonymouswriter HackerNoon profile picture

Ich schreibe diesen Artikel, weil ich keine Lösung gefunden habe, die meiner ähnelt. Daher könnte meine Lösung für jemand anderen nützlich sein.

Inhaltsverzeichnis

  • Durchführung

    • Implementieren der Klassen

    • Verwenden Sie das Statusmuster im React-Hook


  • Der vollständige Code zum Kopieren und Einfügen.


  • Erweiterter Zustandsautomat (Fehlerzustand, kopier- und einfügbares HTML)

    • Diagramm

    • Code


  • Welche Probleme löst es?

  • Warum dieser Artikel sinnvoll ist.

Durchführung

Wir implementieren das State-Designmuster genau wie der Refactoring-Guru empfiehlt: https://refactoring.guru/design-patterns/state

Implementieren der Klassen

 class RoomState { #roomClient = null; #roomId = null; constructor(roomClient, roomId) { if (roomClient) { this.#roomClient = roomClient; } if (roomId) { this.roomId = roomId; } } set roomClient(roomClient) { if (roomClient) { this.#roomClient = roomClient; } } get roomClient() { return this.#roomClient; } set roomId(roomId) { if (roomId) { this.#roomId = roomId; } } get roomId() { return this.#roomId; } join(roomId) { throw new Error('Abstract method join(roomId).'); } leave() { throw new Error('Abstract method leave().'); } getStatusMessage() { throw new Error('Abstract method getStatusMessage().'); } } // ------------------------------------------------------------------------- class PingRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PongRoomState(this.roomClient, roomId)); } leave() { const message = `Left Ping room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Ping room ${this.roomId}`; } } // ------------------------------------------------------------------------- class PongRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { const message = `Left Pong room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Pong room ${this.roomId}`; } } // ------------------------------------------------------------------------- class LeftRoomState extends RoomState { #previousRoom = null; constructor(roomClient, previousRoom) { super(roomClient); this.#previousRoom = previousRoom; } join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { throw new Error(`Can't leave, no room assigned`); } getStatusMessage() { return `Not in any room (previously in ${this.#previousRoom})`; } }

Dies ist unsere Zustandsmaschine bisher

Zustandsmaschinendiagramm


Verwenden Sie das Statusmuster im React Hook

Das nächste Problem: Wie verwenden wir die Klassen in Kombination mit React?

Die anderen Artikel verwenden useEffect und eine Zeichenfolge, um den Namen des aktuellen Status zu speichern. Wir möchten unsere Implementierung sauber halten.

Der roomClient kann den Status ändern, wenn er über einen Verweis auf die Funktion setState verfügt.


Probleme:

  • Wir können den setState nicht übergeben, wenn wir den Status mit der Klasse initialisieren.
  • Wir möchten vom Hook nicht null zurückgeben.
  • Wir möchten keine Mock-Methoden zurückgeben, die vom Hook nichts zurückgeben.


Lösung: Geben Sie den roomClient an, sobald der Status initialisiert ist, direkt unter dem useState .

 function useRoomClient() { const [state, setState] = useState(new PingRoomState()); // State contains the class // Initialize once // We can do this thanks to the `set` and `get` methods on // `roomClient` property if (!state.roomClient) { state.roomClient = { setState }; } return state; }

Der vollständige Code zum Kopieren und Einfügen

 class RoomState { #roomClient = null; #roomId = null; constructor(roomClient, roomId) { if (roomClient) { this.#roomClient = roomClient; } if (roomId) { this.roomId = roomId; } } set roomClient(roomClient) { if (roomClient) { this.#roomClient = roomClient; } } get roomClient() { return this.#roomClient; } set roomId(roomId) { if (roomId) { this.#roomId = roomId; } } get roomId() { return this.#roomId; } join(roomId) { throw new Error('Abstract method join(roomId).'); } leave() { throw new Error('Abstract method leave().'); } getStatusMessage() { throw new Error('Abstract method getStatusMessage().'); } } // ------------------------------------------------------------------------- class PingRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PongRoomState(this.roomClient, roomId)); } leave() { const message = `Left Ping room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Ping room ${this.roomId}`; } } // ------------------------------------------------------------------------- class PongRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { const message = `Left Pong room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Pong room ${this.roomId}`; } } // ------------------------------------------------------------------------- class LeftRoomState extends RoomState { #previousRoom = null; constructor(roomClient, previousRoom) { super(roomClient); this.#previousRoom = previousRoom; } join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { throw new Error(`Can't leave, no room assigned`); } getStatusMessage() { return `Not in any room (previously in ${this.#previousRoom})`; } } function useRoomClient() { const [state, setState] = useState(new PingRoomState()); // State contains the class // Initialize once // We can do this thanks to the `set` and `get` methods on // `roomClient` property if (!state.roomClient) { state.roomClient = { setState }; } return state; }

Erweiterter Zustandsautomat (Fehlerzustand, kopier- und einfügbares HTML)

Wir erweitern die Zustandsmaschine, da wir in Error wechseln möchten, wenn wir versuchen, den Raum zu verlassen und dies zu einer fehlerhaften Operation führt. Sie ermöglicht uns die Anzeige von Statusmeldungen durch den Aufruf getStatusMessage .

Diagramm

aktualisiertes Zustandsdiagramm mit Fehlerstatus



Code

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="root"></div> <script src="https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.development.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.development.js"></script> <script> class RoomState { #roomClient = null; #roomId = null; constructor(roomClient, roomId) { if (roomClient) { this.#roomClient = roomClient; } if (roomId) { this.roomId = roomId; } } set roomClient(roomClient) { if (roomClient) { this.#roomClient = roomClient; } } get roomClient() { return this.#roomClient; } set roomId(roomId) { if (roomId) { this.#roomId = roomId; } } get roomId() { return this.#roomId; } join(roomId) { throw new Error('Abstract method join(roomId).'); } leave() { throw new Error('Abstract method leave().'); } getStatusMessage() { throw new Error('Abstract method getStatusMessage().'); } } // ------------------------------------------------------------------------- class PingRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PongRoomState(this.roomClient, roomId)); } leave() { const message = `Left Ping room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Ping room ${this.roomId}`; } } // ------------------------------------------------------------------------- class PongRoomState extends RoomState { join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { const message = `Left Pong room ${this.roomId}`; this.roomClient.setState(new LeftRoomState(this.roomClient, message)); } getStatusMessage() { return `In the Pong room ${this.roomId}`; } } // ------------------------------------------------------------------------- class LeftRoomState extends RoomState { #previousRoom = null; constructor(roomClient, previousRoom) { super(roomClient); this.#previousRoom = previousRoom; } join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { // Extend to shift to error state this.roomClient.setState( new ErrorRoomState( this.roomClient, new Error(`Can't leave, no room assigned`), ), ); } getStatusMessage() { return `Not in any room (previously in ${this.#previousRoom})`; } } // Extend our state machine to hold one more state. class ErrorRoomState extends RoomState { #error = null; constructor(roomClient, error) { super(roomClient); this.#error = error; } join(roomId) { this.roomClient.setState(new PingRoomState(this.roomClient, roomId)); } leave() { // Do nothing... We can't move anywhere. We handled error. } getStatusMessage() { return `An error occurred. ${this.#error.message}`; } } const { useState } = React; function useRoomClient() { const [state, setState] = useState(new PingRoomState()); // State contains the class // Initialize once // We can do this thanks to the `set` and `get` methods on // `roomClient` property if (!state.roomClient) { state.roomClient = { setState }; } return state; } // ---------------------------------------------------------------------- // Usage example // ---------------------------------------------------------------------- const e = React.createElement; function useWithError(obj) {} function App() { const roomClient = useRoomClient(); return e( 'div', null, e('h1', null, 'Change room state'), e('p', null, `Status message: ${roomClient.getStatusMessage()}`), e( 'div', null, e('button', { onClick: () => roomClient.join('a') }, 'Join'), e('button', { onClick: () => roomClient.leave() }, 'Leave'), ), ); } const { createRoot } = ReactDOM; const root = document.getElementById('root'); createRoot(root).render(React.createElement(App)); </script> </body> </html>

Welche Probleme löst es?

  • Wir können die Zustandsmaschine skalieren, ohne vorhandenen Code zu ändern.
  • Weniger Fehler.
  • Verständlicherer Code, sobald wir verstanden haben, wie er funktioniert (wir müssen lediglich eine neue Klasse für einen neuen Status hinzufügen) .
  • Vermeiden Sie komplizierte if-else-Blöcke, komplexe Zustandsänderungen und eine Switch-Anweisung.
  • Dies ist praktisch, wenn Sie Echtzeiträume mithilfe von WebSockets erstellen möchten (wir können den Verbindungsstatus des Benutzerraums und andere Statustypen überwachen).

Warum dieser Artikel sinnvoll ist

Als ich bei Google nach state design pattern suchte, waren dies meine ersten Ergebnisse

Links zu den 3 Ergebnissen:


Die Suche nach react state design pattern liefert Implementierungen, die überhaupt nicht wie die Implementierung auf https://refactoring.guru/design-patterns/state aussehen.

Links zu den Ergebnissen der Suche: