paint-brush
Hoe u het State Design Pattern in JavaScript implementeert en integreert met React Hooksdoor@anonymouswriter_randomhandleasdad123125156135
Nieuwe geschiedenis

Hoe u het State Design Pattern in JavaScript implementeert en integreert met React Hooks

door anonymouswriter13m2025/03/22
Read on Terminal Reader

Te lang; Lezen

Ik schrijf dit artikel omdat ik geen oplossing heb gevonden die op de mijne lijkt, dus mijn oplossing kan nuttig zijn voor iemand anders. We implementeren het state design pattern precies zoals refactoring guru aanbeveelt. Gebruik het state pattern in de react hook.
featured image - Hoe u het State Design Pattern in JavaScript implementeert en integreert met React Hooks
anonymouswriter HackerNoon profile picture

Ik schrijf dit artikel omdat ik nog geen oplossing heb gevonden die op de mijne lijkt. Mijn oplossing kan dus nuttig zijn voor iemand anders.

Inhoudsopgave

  • Uitvoering

    • Implementeer de klassen

    • Gebruik het statuspatroon in de React-hook


  • De volledige code, zodat u deze kunt kopiëren en plakken.


  • Uitgebreide toestandsmachine (Foutstatus, Kopieer-Plakbare HTML)

    • Diagram

    • Code


  • Welke problemen lost het op?

  • Waarom dit artikel zinvol is.

Uitvoering

We implementeren het state-ontwerppatroon precies zoals de refactoring-goeroe aanbeveelt: https://refactoring.guru/design-patterns/state

Implementeer de 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})`; } }

Dit is onze toestandsmachine tot nu toe

toestandsmachine diagram


Gebruik het State-patroon in de React Hook

Het volgende probleem: hoe gebruiken we de klassen in combinatie met React?

De andere artikelen gebruiken useEffect en een string om de naam van de huidige status op te slaan. We willen onze implementatie schoon houden.

De roomClient kan de status wijzigen als deze een verwijzing naar setState -functie heeft.


Problemen:

  • We kunnen de setState niet doorgeven als we de status initialiseren met de klasse.
  • We willen niet null retourneren vanuit de hook.
  • We willen geen mock-methoden retourneren die niets van de hook retourneren.


Oplossing: geef de roomClient op zodra de status is geïnitialiseerd, direct onder de 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; }

De volledige code, zodat u kunt kopiëren en plakken

 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; }

Uitgebreide toestandsmachine (foutstatus, kopieer-plakbare HTML)

We breiden de toestandsmachine uit omdat we willen overgaan naar de toestand Error als we proberen de kamer te verlaten en dit resulteert in een foutieve bewerking. Hiermee kunnen we statusberichten weergeven door getStatusMessage aan te roepen.

Diagram

bijgewerkt toestandsmachinediagram met foutstatus



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>

Welke problemen lost het op?

  • We kunnen de toestandsmachine schalen zonder de bestaande code te wijzigen.
  • Minder bugs.
  • Begrijpelijkere code, zodra we begrijpen hoe het werkt (het enige wat we hoeven te doen is een nieuwe klasse voor een nieuwe status toevoegen) .
  • Vermijd ingewikkelde if-else-blokken, complexe toestandsmutaties en één switch-statement.
  • Dit is handig als u realtime ruimtes wilt maken met behulp van WebSockets (we kunnen de verbindingsstatus van de gebruikersruimte en andere statussen bewaken).

Waarom dit artikel zinvol is

Toen ik op Google naar state design pattern zocht, waren dit mijn eerste resultaten

Links naar de 3 resultaten:


Als je zoekt naar react state design pattern krijg je implementaties die totaal niet lijken op de implementatie op https://refactoring.guru/design-patterns/state

Links naar de resultaten van de zoekopdracht: