من این مقاله را می نویسم زیرا راه حلی شبیه راه حل من پیدا نکرده ام، بنابراین راه حل من ممکن است برای شخص دیگری مفید باشد. فهرست مطالب پیاده سازی کلاس ها را اجرا کنید از الگوی حالت در قلاب واکنش استفاده کنید کد کامل، بنابراین شما می توانید کپی پیست کنید. ماشین حالت توسعه یافته (وضعیت خطا، HTML قابل کپی-پست) نمودار کد چه مشکلاتی را حل می کند؟ چرا این مقاله منطقی است. پیاده سازی ما الگوی طراحی حالت را درست همانطور که مرشد بازسازی مجدد توصیه می کند پیاده سازی می کنیم: https://refactoring.guru/design-patterns/state کلاس ها را اجرا کنید 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})`; } } این ماشین دولتی ما تا اینجاست از الگوی حالت در React Hook استفاده کنید مشکل بعدی: چگونه از کلاس ها در ترکیب با react استفاده کنیم؟ سایر مقالات از و یک رشته برای ذخیره نام وضعیت فعلی استفاده می کنند. ما می خواهیم پیاده سازی خود را تمیز نگه داریم. useEffect میتواند وضعیت را تغییر دهد، اگر به تابع اشاره داشته باشد. roomClient setState مشکلات: اگر حالت را با کلاس مقداردهی اولیه کنیم، نمی توانیم پاس کنیم. setState ما نمیخواهیم null را از قلاب برگردانیم. ما نمیخواهیم روشهای ساختگی را که هیچ چیز را از هوک برمیگردانند، برگردانیم. راه حل، به محض اینکه حالت اولیه شد، دقیقاً زیر ، ارائه دهید. useState roomClient 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; } کد کامل تا بتوانید کپی-پیست کنید 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; } ماشین حالت توسعه یافته (وضعیت خطا، HTML قابل کپی کردن) ما ماشین حالت را گسترش میدهیم زیرا میخواهیم اگر سعی کنیم اتاق را ترک کنیم به حالت منتقل میشویم و منجر به یک عملیات اشتباه میشود. این به ما امکان می دهد پیام های وضعیت را با تماس گرفتن با نمایش دهیم. Error getStatusMessage نمودار کد <!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> چه مشکلاتی را حل می کند؟ ما میتوانیم ماشین حالت را بدون تغییر کد موجود، مقیاسبندی کنیم. اشکالات کمتر. کد قابل درک تر، زمانی که نحوه عملکرد آن را درک کنیم . (تنها کاری که باید انجام دهیم این است که یک کلاس جدید برای یک وضعیت جدید اضافه کنیم) از بلوکهای پیچیده if-else، جهشهای حالت پیچیده و یک دستور سوئیچ اجتناب کنید. اگر میخواهید با استفاده از WebSockets اتاقهای بیدرنگ ایجاد کنید، خوب است (ما میتوانیم وضعیت اتصال اتاق کاربر و دیگر انواع حالتها را نظارت کنیم). چرا این مقاله منطقی است وقتی در گوگل جستجو کردم، این اولین نتایج من بود state design pattern پیوند به 3 نتیجه: https://refactoring.guru/design-patterns/state https://en.wikipedia.org/wiki/State_pattern https://www.geeksforgeeks.org/state-design-pattern/ https://medium.com/@udaykale/evolving-the-state-design-pattern-e6682a866fdd جستجوی پیاده سازی هایی را ارائه می دهد که هیچ شباهتی به پیاده سازی در ندارند. react state design pattern https://refactoring.guru/design-patterns/state پیوندهایی به نتایج جستجو: https://medium.com/@yah.emam/state-design-pattern-using-react-hooks-c535e1daa6f1 https://blog.logrocket.com/modern-guide-react-state-patterns/ https://github.com/themithy/react-design-patterns/blob/master/doc/state-pattern.md https://refine.dev/blog/react-design-patterns/ https://react.dev/learn/choosing-the-state-structure