من این مقاله را می نویسم زیرا راه حلی شبیه راه حل من پیدا نکرده ام، بنابراین راه حل من ممکن است برای شخص دیگری مفید باشد.
فهرست مطالب
پیاده سازی
کلاس ها را اجرا کنید
از الگوی حالت در قلاب واکنش استفاده کنید
کد کامل، بنابراین شما می توانید کپی پیست کنید.
ماشین حالت توسعه یافته (وضعیت خطا، 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/[email protected]/umd/react.development.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/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