87- آموزش جاوااسکریپت | State Pattern
در این بخش با State Pattern در JavaScript آشنا میشویم؛ الگویی برای مدیریت وضعیتهای مختلف در برنامه. با یک مثال ساده، نحوه تغییر state و نمایش محتوای متفاوت بر اساس وضعیت فعلی را بررسی میکنیم.
در این بخش، الگوی State Pattern را در JavaScript بررسی میکنیم. این الگو یکی از Behavioral Patternها است و برای مدیریت رفتارهای مختلف یک آبجکت بر اساس وضعیت فعلی آن استفاده میشود.
ایده اصلی این الگو ساده است:
برنامه در یک وضعیت مشخص قرار دارد و با تغییر آن وضعیت، رفتار یا خروجی برنامه نیز تغییر میکند.
این مفهوم شباهت زیادی به سیستمهای مدیریت state در برنامههای مدرن دارد؛ یعنی بسته به وضعیت فعلی، میتوانیم محتوای متفاوتی نمایش دهیم یا عملکرد متفاوتی اجرا کنیم.
State Pattern چیست؟
در State Pattern، بهجای اینکه با تعداد زیادی شرط if و switch رفتارهای مختلف را کنترل کنیم، هر وضعیت را در قالب یک ساختار جداگانه تعریف میکنیم.
به این ترتیب:
- هر state مسئول رفتار مربوط به خودش است
- تغییر بین stateها سادهتر میشود
- کد خواناتر و قابلنگهداریتر خواهد شد
- افزودن وضعیتهای جدید آسانتر میشود
سناریوی مثال
برای درک بهتر این الگو، یک مثال ساده میسازیم. فرض کنید یک صفحه داریم که شامل سه بخش اصلی است:
- Home
- About
- Contact
هرکدام از این بخشها را بهعنوان یک state در نظر میگیریم.
با کلیک روی هر لینک، state صفحه تغییر میکند و محتوای نمایشدادهشده در رابط کاربری نیز بهروزرسانی میشود.
در این مثال، همه چیز فقط با JavaScript مدیریت میشود و نیازی به فایل HTML جداگانه برای هر صفحه نداریم.
ساخت PageState
ابتدا یک سازنده به نام PageState تعریف میکنیم تا وضعیت فعلی صفحه را مدیریت کند:
const PageState = function () {
let currentState = new HomeState(this);
this.init = function () {
this.change(new HomeState());
};
this.change = function (state) {
currentState = state;
};
};
بررسی ساختار PageState
در این بخش سه قسمت مهم داریم:
1. متغیر currentState
این متغیر وضعیت فعلی صفحه را نگه میدارد:
let currentState = new HomeState(this);
2. متد init
این متد برای مقداردهی اولیه استفاده میشود و state اولیه را روی HomeState قرار میدهد:
this.init = function () {
this.change(new HomeState());
};
3. متد change
این متد وظیفه تغییر state فعلی را بر عهده دارد:
this.change = function (state) {
currentState = state;
};
تعریف HomeState
اکنون اولین state را تعریف میکنیم:
const HomeState = function (page) {
document.querySelector('#heading').textContent = '';
document.querySelector('#content').innerHTML = `
<div class="jumbotron">
<h1 class="display-4">Welcome</h1>
<p class="lead">This is the home page</p>
</div>
`;
};
عملکرد HomeState
در این state:
- عنوان صفحه خالی میشود
- محتوای اصلی صفحه با یک بخش خوشآمدگویی جایگزین میشود
یعنی هر زمان HomeState فعال شود، رابط کاربری محتوای صفحه اصلی را نمایش میدهد.
تعریف AboutState
حالا state مربوط به بخش About را میسازیم:
const AboutState = function (page) {
document.querySelector('#heading').textContent = 'About Us';
document.querySelector('#content').innerHTML = `
<p>This is the about page</p>
`;
};
عملکرد AboutState
در این state:
- عنوان صفحه روی
About Usتنظیم میشود - یک پاراگراف ساده بهعنوان محتوای این بخش نمایش داده میشود
تعریف ContactState
در ادامه، state مربوط به بخش Contact را پیادهسازی میکنیم:
const ContactState = function (page) {
document.querySelector('#heading').textContent = 'Contact Us';
document.querySelector('#content').innerHTML = `
<form>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" />
</div>
<div class="form-group">
<label>Message</label>
<textarea class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-primary mt-3">Send</button>
</form>
`;
};
عملکرد ContactState
در این وضعیت:
- عنوان صفحه روی
Contact Usقرار میگیرد - یک فرم تماس در بخش محتوا نمایش داده میشود
ساخت نمونهای از PageState
پس از تعریف stateها، باید یک نمونه از PageState بسازیم:
const page = new PageState();
page.init();
با اجرای init()، وضعیت اولیه صفحه روی HomeState قرار میگیرد.
انتخاب عناصر رابط کاربری
برای کنترل لینکهای منو، ابتدا آنها را از DOM انتخاب میکنیم:
const home = document.getElementById('home');
const about = document.getElementById('about');
const contact = document.getElementById('contact');
در این مثال فرض میکنیم هر لینک دارای id مشخصی است:
homeaboutcontact
افزودن Event Listener برای تغییر State
اکنون باید با کلیک روی هر لینک، state صفحه را تغییر دهیم.
رویداد مربوط به Home
home.addEventListener('click', (e) => {
page.change(new HomeState());
e.preventDefault();
});
رویداد مربوط به About
about.addEventListener('click', (e) => {
page.change(new AboutState());
e.preventDefault();
});
رویداد مربوط به Contact
contact.addEventListener('click', (e) => {
page.change(new ContactState());
e.preventDefault();
});
چرا از preventDefault() استفاده میکنیم؟
چون این لینکها بهصورت پیشفرض رفتار ناوبری مرورگر را اجرا میکنند.
با استفاده از preventDefault() جلوی این رفتار گرفته میشود تا بتوانیم تغییر صفحه را فقط با JavaScript مدیریت کنیم.
نسخه کامل کد JavaScript
در ادامه، کل پیادهسازی را یکجا میبینید:
const PageState = function () {
let currentState = new HomeState(this);
this.init = function () {
this.change(new HomeState());
};
this.change = function (state) {
currentState = state;
};
};
const HomeState = function (page) {
document.querySelector('#heading').textContent = '';
document.querySelector('#content').innerHTML = `
<div class="jumbotron">
<h1 class="display-4">Welcome</h1>
<p class="lead">This is the home page</p>
</div>
`;
};
const AboutState = function (page) {
document.querySelector('#heading').textContent = 'About Us';
document.querySelector('#content').innerHTML = `
<p>This is the about page</p>
`;
};
const ContactState = function (page) {
document.querySelector('#heading').textContent = 'Contact Us';
document.querySelector('#content').innerHTML = `
<form>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" />
</div>
<div class="form-group">
<label>Message</label>
<textarea class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-primary mt-3">Send</button>
</form>
`;
};
const page = new PageState();
page.init();
const home = document.getElementById('home');
const about = document.getElementById('about');
const contact = document.getElementById('contact');
home.addEventListener('click', (e) => {
page.change(new HomeState());
e.preventDefault();
});
about.addEventListener('click', (e) => {
page.change(new AboutState());
e.preventDefault();
});
contact.addEventListener('click', (e) => {
page.change(new ContactState());
e.preventDefault();
});
ساختار ساده HTML
برای اینکه این مثال کار کند، به ساختار HTML سادهای شبیه نمونه زیر نیاز داریم:
<nav>
<a id="home" href="#">Home</a>
<a id="about" href="#">About</a>
<a id="contact" href="#">Contact</a>
</nav>
<div class="container">
<h1 id="heading"></h1>
<div id="content"></div>
</div>
مزایای State Pattern
استفاده از این الگو مزایای مهمی دارد:
- حذف شرطهای پیچیده و تودرتو
- خوانایی بهتر کد
- جدا شدن منطق هر وضعیت
- توسعه آسانتر
- نگهداری سادهتر در پروژههای بزرگتر
کاربردهای رایج State Pattern
این الگو در سناریوهای مختلفی کاربرد دارد، از جمله:
- مدیریت وضعیت صفحات در رابط کاربری
- فرمهای چندمرحلهای
- ویزاردها و فرآیندهای مرحلهبهمرحله
- مدیریت وضعیت دکمهها و کامپوننتها
- سیستمهای دارای حالتهایی مثل
loading،success،error - پنلهای مدیریتی و اپلیکیشنهای تعاملی
نکته مهم
مثالی که در این بخش دیدیم، یک نمونه ساده برای درک مفهوم State Pattern است. در پروژههای واقعی، معمولاً stateها کاربردهای عملیتری دارند؛ برای مثال:
loadingStateeditStatelistStateerrorState
اما همین مثال ساده بهخوبی نشان میدهد که چگونه میتوان با استفاده از stateها، بدون ساخت فایلهای جداگانه، محتوای رابط کاربری را کنترل کرد.
جمعبندی
در این بخش با State Pattern در JavaScript آشنا شدیم. این الگو به ما کمک میکند رفتار برنامه را بر اساس وضعیت فعلی آن مدیریت کنیم.
در مثال این آموزش دیدیم که:
- هر بخش از صفحه یک state جداگانه است
- با تغییر state، محتوای UI نیز تغییر میکند
- همه این رفتارها بدون نیاز به صفحات HTML جداگانه انجام میشوند
اگر در پروژهای با وضعیتهای مختلف روبهرو هستید و میخواهید منطق هر وضعیت را بهصورت جداگانه و تمیز مدیریت کنید، State Pattern یکی از الگوهای بسیار کاربردی در JavaScript است.