زبان صفحهی سلام بخشها (component)، مدیریت حالت (state management) و مدیریت رویداد را بدون یک چارچوب جداگانهی سمت کاربر فراهم میکند. صفحه را به شکل درختی از عنصرها اعلام میکنید، رویدادگیرها را مثل تابعهای معمولی زبان برنامهنویسی سلام مینویسید و کامپایلر کل آن را به HTML، CSS و جاوااسکریپت تبدیل میکند. بخش در سلام تکهای نامدار از صفحه با پارامترهای خودش است؛ وقتی متغیری تعریف کند که رویدادگیری آن را تغییر میدهد و نما آن را نشان میدهد، آن متغیر حالت میشود و صفحه فقط چیزی را دوباره رسم میکند که به آن وابسته است. همین صفحه میتواند در پنجرهای بومی هم اجرا شود که خود سلام آن را رسم میکند.
این مقاله الگو را گام به گام پیش میبرد، از یک رویدادگیر کلیک ساده تا بخشهایی که حالت خودشان را نگه میدارند و با هم حرف میزنند، و با بررسیهایی که کامپایلر انجام میدهد و محدودیتهایی که باید بدانید تمام میشود. هر برنامهی این مقاله یک صفحهی کامل است که با salam page build ساخته میشود.
تعامل در صفحهی سلام چطور کار میکند
صفحهی سلام یک بلوک صفحه: است. عنصرها با دونقطه باز و با پایان بسته میشوند و ویژگیها و خاصیتهای CSS به شکل نام = مقدار درونشان میآیند. همین برای HTML و CSS ایستا کافی است؛ مقالهی توسعهی وب با سلام آن بخش را پوشش میدهد.
تعامل از راه ویژگیهایی وارد میشود که نامشان رویدادی از مرورگر است. کلیک، ارسال، تغییر، فشردن کلید و بقیه به جای مقدار یک تابع میگیرند. نام سادهی یک تابع، یا تابعی که درجا نوشته شود، با پشتوانهی جاوااسکریپت سلام (JavaScript Backend) کامپایل میشود و در اسکریپت خود صفحه مینشیند. هرگز جاوااسکریپت را با دست نمینویسید و رویدادگیر مثل هر کد دیگر سلام بررسی گونه میشود.
برای اینکه صفحه پس از یک رویداد تغییر کند دو راه هست و سلام از هر دو پشتیبانی میکند:
- راه مستقیم. رویدادگیر با بستهی
دام(dom) عنصری را پیدا میکند و متن، کلاس، سبک یا ویژگیهایش را تغییر میدهد. این همان سبک آشنای DOM است، و درسهای رویدادهای ماوس، صفحهکلید و فرمها، کشیدن و لمس و رویدادهای رسانه با آن هر خانواده از رویدادها را پوشش میدهند. - راه واکنشی. رویدادگیر فقط متغیرها را تغییر میدهد. کامپایلر میداند کدام بخشهای صفحه آن متغیرها را میخوانند و دقیقاً همانها را دوباره رسم میکند. بخشهای حالتدار اینطور کار میکنند، و این تنها سبکی است که در پنجرهی بومی هم اجرا میشود.
بقیهی مقاله از راه اول شروع میکند و به راه دوم میرسد.
مدیریت رویداد در زبان سلام با بستهی دام
کوچکترین صفحهی تعاملی مفید این است: یک عدد و دو دکمه. حالت یک متغیر ناپایا بالای صفحه است. هر رویدادگیر آن را تغییر میدهد و نشاندادن را صدا میزند، تنها تابعی که در صفحه مینویسد.
واردسازی دام
ناپایا شمار := ۰
تابع نشاندادن:
دام.تنظیم متن(دام.با شناسه("مقدار")، "" + شمار)
پایان
تابع بیشتر:
شمار++
نشاندادن()
پایان
تابع ازنو:
شمار = ۰
نشاندادن()
پایان
صفحه:
عنوان = "شمارشگر"
جعبه:
سرتیتر: شناسه = "مقدار" محتوا = "0" پایان
دکمه: کلیک = بیشتر محتوا = "+1" پایان
دکمه: کلیک = ازنو محتوا = "از نو" پایان
پایان
پایانسه نکته مهم است. اول، کلیک = بیشتر نام تابع را میدهد و آن را صدا نمیزند. نوشتن کلیک = بیشتر() رد میشود، چون ویژگی خود تابع را میخواهد (خطای E137؛ اگر تابع حالت صفحه را تغییر دهد، کامپایلر همین اشتباه را با E132 گزارش میکند). دوم، دام.با شناسه("مقدار") با شناسههایی که واقعاً در همین پرونده وجود دارند مقایسه میشود، پس غلط تایپی مثل دام.با شناسه("مقدرا") پیش از اینکه صفحه به مرورگر برسد ساخت را با E138 متوقف میکند. سوم، صفحه ریشه ندارد. برنامهی صفحه تماماً با رویدادگیرهایش پیش میرود، و افزودن تابع ریشه باعث میشود کامپایلر رویدادگیرها را تابعهای بیاستفاده گزارش کند.
بستهی دام آنچه را رویدادگیر معمولاً لازم دارد پوشش میدهد: پیدا کردن عنصرها (با شناسه، پرسمان، پرسمان همه)، خواندن و نوشتن متن و مقدار، کلاسها و سبکها، ساختن و حذف عنصرها، زمانسنجها (تنظیم مهلت، تنظیم بازه، درخواست فریم)، خود رویداد (هدف رویداد، کلید رویداد، جلوگیری پیش فرض) و حافظهی مرورگر (تنظیم متن ذخیره، دریافت متن ذخیره). فهرست کامل در صفحهی بستهی dom آمده و درس حالت، DOM و زمانسنجها با آن یک فهرست کارها، زبانهها، یک زمانسنج و یک آزمونک میسازد.
سبک مستقیم برای صفحههای کوچک خوب کار میکند. نقطهضعفش همان است که هر توسعهدهندهی دوران jQuery به یاد دارد: همین که دو رویدادگیر یک عنصر را تغییر دهند، باید متغیرها و صفحه را با دست همگام نگه دارید. درس سلام برای این کار یک انضباط پیشنهاد میکند (حالت در متغیرها، یک تابع رسم)، و الگوی واکنشی پایینتر نیاز به آن را از بین میبرد.
بخشها: تکههای قابل استفادهی دوبارهی صفحه
بخش تکهای از صفحه است که یک بار تعریفش میکنید و مثل یک عنصر تازه به کارش میبرید. پارامترهایش مثل ویژگیها مقدار میگیرند. درون آن، محتوا: پایان یک جای خالی را نشان میدهد: هر چه هنگام استفاده درون بخش بگذارید همانجا مینشیند.
بخش طرح(نام: رشته، بها: صحیح):
جعبه:
کادر = "1px solid #ddd"
گردی کادر = ۱۲
فاصله = ۱۶
سرتیتر: اندازه = ۲ محتوا = نام پایان
پاراگراف: محتوا = "ماهانه " + بها + " دلار" پایان
محتوا: پایان
پایان
پایان
صفحه:
عنوان = "طرحها"
طرح: نام = "پایه" بها = ۴ پایان
طرح: نام = "حرفهای" بها = ۹
فهرست:
مورد: محتوا = "پروژههای خصوصی" پایان
مورد: محتوا = "پشتیبانی با رایانامه" پایان
پایان
پایان
پایانهر دو طرح یک کلاس CSS ساختگی مشترک دارند، چون سبکشان یکسان است. بخشها میتوانند در پروندهای جدا باشند و با بگنجان = "parts.salam" بالای صفحه وارد شوند؛ سایت اینطور یک سرصفحه یا کارت را میان صفحهها به اشتراک میگذارد. در انگلیسی واژهی کلیدی component است و هر عنصر و ویژگی نام انگلیسی هم دارد؛ مقالهی یک زبان، دو خط توضیح میدهد چرا هر دو املا به یک چیز کامپایل میشوند.
بخشی که پارامترهای گونهدار دارد یک تابع هم هست. اگر از کد معمولی سلام صدا زده شود، طرح("حرفهای"، ۹) HTML و CSS خودش را به شکل یک رشته برمیگرداند، و سرور میتواند همین تکه از رابط کاربری را برای هر درخواست بسازد.
حالت درون بخشها
بخش وقتی تعاملی میشود که بالای بدنهاش متغیری تعریف کند. نه useState در کار است، نه سازندهی سیگنال و نه واژهی کلیدی خاصی. کامپایلر نگاه میکند هر متغیر چطور به کار رفته و آن را دستهبندی میکند:
| کدی آن را مینویسد؟ | نما آن را نشان میدهد؟ | چه میشود |
|---|---|---|
| بله | بله | حالت: برای هر نمونه جدا نگه داشته میشود و نمونه با تغییرش دوباره رسم میشود |
| بله | نه | متغیر نمونه: برای هر نمونه جدا نگه داشته میشود ولی هرگز رسم دوباره را راه نمیاندازد |
| نه | فرقی نمیکند | مقدار مشتق: هر بار که نمونه رسم میشود دوباره محاسبه میشود |
«نوشتن» یعنی انتساب، ++، متدی مثل بیفزا، یا تغییری که درون یک رویدادگیر یا فراخوان بازگشتی انجام شود. «نشان دادن» یعنی متغیر در یک ویژگی، یک متن، شرط یک اگر، مجموعهی یک هر یا مقداری که به بخش فرزند داده میشود ظاهر شود.
بخش نامزد(نام: رشته):
رایها := ۰
متن دکمه := "رأی به " + نام
جعبه:
کلاس = "choice"
دکمه: کلیک = (): رایها++ پایان محتوا = متن دکمه پایان
تکه: محتوا = "" + رایها پایان
اگر رایها >= ۳:
تکه: کلاس = "hot" محتوا = "پرطرفدار" پایان
پایان
پایان
پایان
صفحه:
عنوان = "نظرسنجی"
سرتیتر: اندازه = ۲ محتوا = "نوشیدنی محبوبتان چیست؟" پایان
نامزد: نام = "چای" پایان
نامزد: نام = "قهوه" پایان
پایانرویدادگیر کلیک رایها را مینویسد و تکه و اگر آن را نشان میدهند، پس حالت است: هر نامزد شمارش خودش را دارد و کلیک روی یکی فقط همان نمونه را دوباره رسم میکند. متن دکمه هرگز نوشته نمیشود، پس از نام مشتق میشود. هیچ چیز دیگری در صفحه دست نمیخورد؛ یعنی تمرکز روی دکمه میماند و متنی که در ورودی نمونهی دیگری تایپ شده سر جایش باقی است.
متغیری که نوشته میشود گونهای لازم دارد که کامپایلر ببیند: یک لفظ، یک پارامتر گونهدار، یا مقدار برگردان گونه، مثل موارد := وکتور {} برگردان وکتور<رشته>. بلوک صفحه: هم میتواند به همین شکل متغیر تعریف کند، و صفحه میتواند متغیرهای ناپایای سطح بالا را هم بخواند؛ هر عنصر، اگر یا هری که یکی از آنها را بخواند پس از تغییرش در رویدادگیر دوباره رسم میشود.
فهرستها، کلیدها و رندر شرطی
اگر و هر درون صفحه همانجاییاند که رابط کاربری اعلانی ارزشش را نشان میدهد. صفحهی بعدی فهرستی از کارها را در یک وکتور ناپایا نگه میدارد، تا وقتی خالی است پیامی نشان میدهد و برای هر مورد یک بخش کار رسم میکند. هر کار حالت انجامشده خودش را دارد.
ناپایا کارها := وکتور {} برگردان وکتور<رشته>
ناپایا شماره بعد := ۱
بخش کار(نام: رشته):
انجامشده := نادرست
مورد:
کلید = نام
تکه: محتوا = نام پایان
دکمه: کلیک = (): انجامشده = وارونه انجامشده پایان محتوا = انجامشده ? "برگرداندن" : "انجام شد" پایان
پایان
پایان
صفحه:
عنوان = "کارها"
دکمه:
محتوا = "افزودن کار"
کلیک = ():
کارها.بیفزا("کار " + شماره بعد)
شماره بعد++
پایان
پایان
اگر کارها.طول() == ۰:
پاراگراف: محتوا = "هنوز کاری نیست" پایان
پایان
فهرست:
هر ک در کارها:
کار: نام = ک پایان
پایان
پایان
پاراگراف: محتوا = "" + کارها.طول() + " کار" پایان
پایانخط کلید = نام همان ایدهی key در React است: وقتی ردیفها اضافه، حذف یا جابهجا میشوند، هر نمونهی بخش دنبال کلیدش میرود و حالت خودش را نگه میدارد، به جای اینکه بر اساس جایگاهش جفت شود. بدون کلید، نمونهها جایگاهشان در فهرست را دنبال میکنند. شناسهی ثابت درون هر تکرار میشد، پس کامپایلر آن را رد میکند (E134)؛ از کلید، کلاس یا شناسهای که از روی ردیف ساخته میشود استفاده کنید.
گفتوگوی بخشها با هم: فراخوان بازگشتی و رویداد
بخش فرزند اغلب باید به والدش خبر دهد که اتفاقی افتاده است. سلام دو سازوکار برای این کار دارد و هر دو در زمان کامپایل حل میشوند.
اولی فراخوان بازگشتی (callback) است: والد یک تابع را بهعنوان پارامتر میفرستد و فرزند آن را از رویدادگیرش صدا میزند. لامبدا در والد نوشته میشود، پس حالت والد را تغییر میدهد. فرزندی که والدش چیزی نفرستاده باشد صدا زدن را ساده رد میکند.
دومی رویداد بالارونده است. فرزند مینویسد برانگیز("افزوده شد"، بها) و جایی که بخش به کار رفته آن را با هنگام افزوده شد = (ب: صحیح): ... پایان میگیرد. رویداد به نزدیکترین رویدادگیر میرسد و سپس به همهی سطحهای بالاتر، تا خط هنگام افزوده شد بالای بلوک صفحه، مگر اینکه یکی از رویدادگیرها بایست() را صدا بزند.
ناپایا جمع کل := ۰
بخش شمارگر(برچسب متن: رشته، اطلاع):
شمار := ۰
جعبه:
تکه: محتوا = برچسب متن + ": " + شمار پایان
دکمه:
محتوا = "+1"
کلیک = ():
شمار++
اطلاع(۱)
پایان
پایان
پایان
پایان
بخش کالا(نام: رشته، بها: صحیح):
جعبه:
تکه: محتوا = نام + "، " + بها + " سکه" پایان
دکمه: کلیک = (): برانگیز("افزوده شد"، بها) پایان محتوا = "به سبد بیفزا" پایان
پایان
پایان
صفحه:
عنوان = "سبد خرید"
هنگام افزوده شد = (ب: صحیح): جمع کل += ب پایان
سرتیتر: اندازه = ۲ محتوا = "سبد: " + جمع کل + " سکه" پایان
کالا: نام = "چای" بها = ۳ پایان
کالا: نام = "کیک" بها = ۵ پایان
شمارگر: برچسب متن = "فنجان اضافه" اطلاع = (ت: صحیح): جمع کل += ت پایان پایان
پایانچون کامپایلر مسیر هر رویداد را هنگام ساختن صفحه پیدا میکند، برانگیز هزینهای بیش از صدا زدن مستقیم رویدادگیرها ندارد و فقط بخشهایی دوباره رسم میشوند که حالتشان تغییر کرده است. در انگلیسی برانگیز، هنگام و بایست به ترتیب emit، on و stop نوشته میشوند.
بررسی رابط کاربری در زمان کامپایل
رابط کاربریای که در مرورگر اشتباه است معمولاً بیصدا اشتباه است: شناسهی غلط null برمیگرداند، رویدادگیری هرگز وصل نمیشود، سبکی نادیده گرفته میشود. سلام تا جای ممکن این اشتباهها را به زمان ساخت میآورد. چند بررسی که به صفحههای تعاملی مربوطاند:
| کد | چه چیزی را میگیرد |
|---|---|
| E137 | ویژگی رویدادی که به جای تابع عبارتی مثل بیشتر() گرفته است |
| E138 | دام.با شناسه("x") وقتی هیچ عنصری در پرونده شناسهی x ندارد |
| E111 / E112 | عنصر یا ویژگی ناشناخته، با پیشنهاد «منظورتان ... بود؟» |
| E133 | اگر یا هر در صفحهی ایستا، جایی که هیچ مقدار زمان اجرایی وجود ندارد |
| E134 | شناسهی ثابت درون هر که تکرار میشود |
| E136 | یک کلاس که روی دو عنصر سبکهای متفاوت گرفته است |
| E018 | مقدار CSS با گونهی نادرست، مثل رنگ به جای طول |
این دو مورد اول روی صفحهای است که رویدادگیرش را اشتباهی صدا میزند و دنبال شناسهای میگردد که وجود ندارد:
$ salam page build counter.salam
E137: ویژگی رویداد 'کلیک' باید نام یک تابع، یک تابع درونخطی یا یک رشتهی JS باشد، نه یک عبارت (counter.salam:11:30)
E138: در صفحهی این فایل عنصری با شناسهی 'شمارنده' نیست (counter.salam:4:53)
ساخت صفحه با 2 خطا ناموفق بود؛ هیچ خروجیای نوشته نشدبررسی شناسه یک محدودیت دارد که دانستنش میارزد: فقط شناسههایی را میبیند که به شکل متن ساده نوشته شدهاند. دام.با شناسه("votes-" + نام) در زمان اجرا ساخته میشود و بررسیپذیر نیست، پس غلط تایپی در آن همچنان در کنسول مرورگر پیدا میشود. خود رویدادگیرها کد سلاماند، پس گونهی نادرست، تابع ناشناخته یا آرگومان جاافتاده یک خطای کامپایل معمولی است، درست مثل برنامهی سرور.
یک صفحه، دو مقصد: وب و پنجرهی بومی
الگوی واکنشی فایدهی دومی هم دارد. صفحهای که رویدادگیرهایش فقط حالت را تغییر میدهند به مرورگر وابسته نیست، پس سلام میتواند آن را در یک پنجرهی بومی اجرا کند:
salam page build tasks.salam # tasks.html، tasks.css و tasks.js
salam run tasks.salam --ui=native # همان صفحه در پنجرهی بومی
salam build tasks.salam --ui=native # یک فایل اجرایی بومی
salam check tasks.salam --ui=native # گزارش چیزهایی که پنجره نمیتواند نشان دهداجرای بومی بر عهدهی بستهی gui.ui است که روی موتور CSS، چیدمان جعبه، شکلدهی متن و رسام خود سلام ساخته شده، پس هیچ موتور مرورگری همراه برنامه نمیآید و لازم هم نیست. پس از هر رویدادگیر نما را دوباره اجرا میکند و نتیجه را با آنچه روی صفحه است تطبیق میدهد، پس حالت اشاره، تمرکز، جایگاه پیمایش و متن تایپشده حفظ میشوند. یادداشتهای طراحی پروژه این روش را با Svelte و Solid مقایسه میکنند: کامپایلر پیدا میکند هر بخش صفحه چه چیزی را میخواند، هر نوشتن را علامت میزند و فقط بخشهایی که حالتشان تغییر کرده دوباره اجرا میشوند.
هر عنصری در پنجره معنا ندارد. salam check --ui=native برای چیزهایی که مرورگر لازم دارند، مثل اسکریپت، iframe و svg، خطای E165 و برای فرادادهی وب که پنجره نادیدهاش میگیرد هشدار W166 میدهد. پیچیدن این بخشها در اگر سلام رابط وب: یک پروندهی منبع را برای هر دو مقصد نگه میدارد. سبک مستقیم دام در مثال اول ذاتاً فقط در مرورگر کار میکند. مقالهی برنامههای رومیزی و رابط گرافیکی به پنجرهها، برنامههای WebView و نصبکنندهها بیشتر میپردازد.
مقایسه با React و Vue، و محدودیتهای واقعی
اگر React، Vue یا Svelte را میشناسید، مفهومها نزدیک به هماند: بخشهایی با پارامتر (props)، حالت محلی، فهرستهای کلیددار، فراخوان بازگشتی که پایین فرستاده میشود و رویدادهایی که بالا میروند. تفاوت در جایی است که کار انجام میشود. سلام نه هوک (hook) دارد و نه سیگنال صریح؛ کامپایلر از روی نحوهی استفاده از یک متغیر تصمیم میگیرد که حالت است یا نه. ابزار ساخت جداگانه، بستهساز (bundler) یا بستهی کد رابط کاربری برای نصب وجود ندارد: صفحه، CSS و رویدادگیرهایش از یک کامپایلر بیرون میآیند. و همان بررسیکنندهی گونهای که کد سرور را بررسی میکند رویدادگیرها را هم بررسی میکند.
محدودیتها واقعیاند و باید سنجیده شوند:
- اکوسیستم بخش وجود ندارد. چیزی شبیه هزاران انتخابگر تاریخ، جدول داده و کتابخانهی نمودار آمادهی npm در کار نیست. بخشها را خودتان میسازید یا جاوااسکریپت موجود را از راه
دام.سراسریو تابعهایدام.فراخوانمیپیچید که گونه ندارند. - بخشهای حالتدار در وب به جاوااسکریپت نیاز دارند. نمونههای بخش و بخشهایی که متغیرهای
ناپایارا میخوانند با اسکریپت صفحه رسم میشوند، پس فقط با جاوااسکریپت روشن دیده میشوند. بخشهای ایستا HTML سادهاند. - رشتهها در مرورگر بایت میشمارند.
رشتهی سلام که به جاوااسکریپت کامپایل شود بایتهای UTF-8 را میشمارد، پس طول واژهی فارسیسلامبرابر ۸ است. برای آنچه خواننده میبیند، خاصیتهای خود مرورگر مثلtextLengthرا بخوانید. برای صفحههای فارسی این نکته بیشتر از صفحههای انگلیسی به چشم میآید. - کمکتابع آمادهای برای fetch وجود ندارد. بستهی
دامتابعfetchرا نمیپیچد، پس صفحه به جای فراخوانی API از سمت کاربر، از راه فرمها و صفحههایی که سرور رندر میکند با سرور حرف میزند؛ مقالهی توسعهی فولاستک با سلام را ببینید. - زبانی جوان. زبان صفحه و اجرای بومی تازهاند و میان نسخهها هنوز تغییر میکنند.
برای پروژهی آموزشی، ابزار داخلی سازمان، صفحهی فرودی با چند تکهی تعاملی یا برنامهی کوچکی که باید در پنجرهی بومی هم اجرا شود، این الگو خوشایند است و بررسیهای زمان کامپایل اشتباههای واقعی را میگیرند. برای یک برنامهی تکصفحهای بزرگ و سنگین در سمت کاربر که به کتابخانهی غنی بخشها وابسته است، یک چارچوب رایج جاوااسکریپت امروز همچنان انتخاب عملی است.
پرسشهای پرتکرار
آیا زبان سلام مثل React یک DOM مجازی دارد؟
در وب، هر نمونهی حالتدار بخش خودش را در اسکریپت صفحه رسم میکند و پس از یک رویدادگیر فقط نمونههایی که حالتشان تغییر کرده دوباره رسم و درجا روی عنصرهای موجود اعمال میشوند. کامپایلر هنگام ساخت میداند کدام بخشها کدام متغیرها را میخوانند، پس یک کلیک به جای کل صفحه فقط یک نمونهی بخش را دوباره رسم میکند.
حالت سراسری را در صفحهی سلام چطور مدیریت کنم؟
از متغیرهای ناپایای سطح بالا بالای بلوک صفحه: یا متغیرهایی که بالای خود بلوک صفحه تعریف میشوند استفاده کنید. هر عنصر، اگر یا هری که یکی از آنها را بخواند پس از تغییرش در رویدادگیر دوباره رسم میشود. مقدارها را با پارامتر به بخشها بدهید و تغییرها را با فراخوان بازگشتی یا برانگیز بالا بفرستید.
آیا میتوانم بخشهای سلام را کنار یک برنامهی جاوااسکریپت موجود به کار ببرم؟
صفحهی سلام به HTML، CSS و جاوااسکریپت معمولی کامپایل میشود، پس میتواند کنار اسکریپتهای دیگر بنشیند. صدا زدن جاوااسکریپت موجود از یک رویدادگیر از راه دام.سراسری و تابعهای دام.فراخوان انجام میشود، یا با یک رویدادگیر رشتهای ساده مثل کلیک = "alert(42)".
زبان صفحهی سلام را از کجا گام به گام یاد بگیرم؟
آموزش رایگان سلام بخش کاملی دربارهی صفحههای وب دارد، از زبان صفحه و ساختار صفحه تا CSS، درسهای رویداد و شش پروژهی کامل وب، همه به فارسی و هر کدام با پیشنمایش زنده.