پیمایش¶
به تازگی دیدیم که چگونه یک صفحه را میزبانی کنیم، اما فرض کنید که در حال ساخت وبسایتی مانند package.elm-lang.org هستیم. این وبسایت دارای چندین صفحه است (برای نمونه جستجو، راهنما و مستندات) که همه بطور متفاوتی کار میکنند. چگونه این کار انجام میشود؟
ساختار چند صفحهای¶
سادهترین روش این است که تعدادی فایل HTML مختلف را میزبانی کنیم. به صفحه اصلی میروید؟ HTML جدید بارگیری کنید. به صفحه مستندات elm/core میروید؟ HTML جدید بارگیری کنید. به صفحه مستندات elm/json میروید؟ HTML جدید بارگیری کنید.
تا نسخه Elm 0.19، دقیقا همین کار انجام میشد! این کار ساده است، اما چند نقطه ضعف دارد:
- صفحه خالی: با هر مرتبه بارگیری HTML جدید، صفحه سفید میشود. آیا میتوان بجای آن یک انتقال زیبا انجام داد؟
- درخواست اضافی: هر بسته یک فایل
docs.jsonدارد، اما هر مرتبه که به یک ماژول مانندStringیاMaybeمراجعه میکنید، بارگیری میشود. آیا میتوان دادهها را بین صفحات به اشتراک گذاشت؟ - کد اضافی: صفحه اصلی و مستندات، توابع مشترکی مانند
Html.textوHtml.divدارند. آیا میتوان این کد را بین صفحات به اشتراک گذاشت؟
میتوانیم هر سه مورد را بهبود ببخشیم! ایده اصلی این است که HTML را فقط یک مرتبه بارگیری، سپس کمی با URL تغییرات را مدیریت کنیم.
ساختار تک صفحهای¶
بجای ایجاد برنامه با Browser.element یا Browser.document، میتوانیم یک Browser.application ایجاد کرده تا از بارگیری HTML جدید هنگام تغییر URL جلوگیری کنیم:
application :
{ init : flags -> Url -> Key -> ( model, Cmd msg )
, view : model -> Document msg
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, onUrlRequest : UrlRequest -> msg
, onUrlChange : Url -> msg
}
-> Program flags model msg
این کار منجر به گسترش عملکرد Browser.document در سه سناریوی مهم میشود:
-
زمانی که برنامه شروع میشود، تابع
initمقدار URL فعلی را از نوار پیمایش مرورگر وب دریافت میکند. این کار به شما اجازه میدهد با توجه به مقدارUrlچیزهای مختلفی را نمایش دهید. -
زمانی که روی یک لینک کلیک میشود، مانند
<a href="/home">Home</a>، یک درخواستUrlRequestصادر میشود. بنابراین، بجای بارگیری HTML جدید با تمام معایب آن،onUrlRequestیک پیام برای تابعupdateایجاد میکند که میتوان دقیقا تصمیم گرفت چه کاری باید انجام شود. برای نمونه، میتوان موقعیت اسکرول را ذخیرهسازی کرد، دادهها را در مرورگر وب حفظ کرد یا URL را تغییر داد. -
زمانی که URL تغییر میکند،
Urlجدید بهonUrlChangeارسال میشود. پیام حاصل به تابعupdateمیرود که در آن میتوان تصمیم گرفت چگونه صفحه جدید را نمایش دهیم.
بنابراین، بجای بارگیری HTML، این سه قابلیت جدید به شما امکان کنترل کامل بر تغییرات URL را میدهند. بیایید آن را در عمل ببینیم!
نمونه کد¶
با برنامه پایه Browser.application شروع میکنیم. این برنامه فقط URL فعلی را پیگیری میکند. اکنون کد را مرور کنید! تقریبا تمام چیزهای جدید و جالب در تابع update اتفاق میافتد که در ادامه به جزییات آن خواهیم پرداخت:
تابع update میتواند پیامهای LinkClicked یا UrlChanged را مدیریت کند. در شاخه LinkClicked چیزهای جدید و جالب زیادی وجود دارد، بنابراین ابتدا بر روی آن تمرکز میکنیم!
UrlRequest¶
هر زمان که روی یک لینک مانند <a href="/home">/home</a> کلیک شود، یک مقدار UrlRequest تولید میشود:
حالت Internal برای هر لینکی است که در همان دامنه باقی میماند. بنابراین، اگر در حال مرور https://example.com هستید، لینکهای داخلی شامل چیزهایی مانند settings#privacy، /home، https://example.com/home و //example.com/home هستند.
حالت External برای هر لینکی است که به دامنهای دیگر میرود. لینکهایی مانند https://elm-lang.org/examples، https://static.example.com و https://example.com/home همه به دامنهای دیگر میروند. توجه داشته باشید که تغییر پروتکل از https به http به عنوان یک تغییر دامنه در نظر گرفته میشود!
هر لینکی که کلیک شود، برنامه یک پیام LinkClicked ایجاد کرده و آن را به تابع update ارسال میکند. اینجاست که بیشتر کدهای جدید و جالب را میبینیم!
LinkClicked¶
بیشتر منطق تابع update تصمیمگیری درباره این است که با مقادیر UrlRequest چه کاری انجام شود:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
( model, Nav.pushUrl model.key (Url.toString url) )
Browser.External href ->
( model, Nav.load href )
UrlChanged url ->
( { model | url = url }
, Cmd.none
)
دو تابع Nav.pushUrl و Nav.load عملکرد جالبی دارند. اینها هر دو از ماژول Browser.Navigation هستند که درباره تغییر URL به روشهای مختلف است. ما از دو تابع رایج آن ماژول استفاده میکنیم:
- تابع
pushUrlمقدار URL را تغییر میدهد، اما HTML جدید بارگیری نمیشود. در عوض، یک پیامUrlChangedرا ایجاد میکند که خودمان آن را مدیریت میکنیم! همچنین یک ورودی به "تاریخچه مرورگر" اضافه میکند تا وقتی که کاربران دکمههایBACKیاFORWARDرا فشار میدهند، همه چیز بطور عادی کار کند. - تابع
loadتمام HTML جدید را بارگیری میکند. این کار معادل تایپ کردن URL در نوار پیمایش و فشردن Enter است. بنابراین، هر چیزی که درModelحاضر وجود دارد، دور ریخته و یک صفحه کاملا جدید بارگیری میشود.
با نگاهی به تابع update، اکنون میتوانیم درک بهتری از چگونگی ارتباط اجزا با یکدیگر داشته باشیم. وقتی کاربر روی لینک home/ کلیک میکند، یک پیام Internal دریافت و از تابع pushUrl برای تغییر URL بدون بارگیری HTML جدید استفاده میکنیم. وقتی کاربر روی لینک https://elm-lang.org کلیک میکند، یک پیام External دریافت و از تابع load برای بارگیری HTML جدید در سرور استفاده میکنیم.
نکته
هر دو حالت Internal و External بلافاصله دستورات خود را تولید میکنند، اما این کار الزامی نیست! وقتی روی یک لینک Internal کلیک میشود، شاید بخواهید از getViewport برای ذخیرهسازی موقعیت اسکرول استفاده کنید تا در صورت فشردن دکمه BACK، آن را حفظ کنید. وقتی روی یک لینک External کلیک میشود، شاید بخواهید محتوای جعبه متن را قبل از رفتن به صفحه دیگر در پایگاه داده ذخیرهسازی کنید. همه اینها ممکن است! این یک تابع update عادی است که میتوانید وضعیت پیمایش را به تاخیر بیندازید و هر کاری که میخواهید انجام دهید.
اگر میخواهید "آنچه را که کاربران در حال مشاهده بودند" هنگام بازگشت به عقب بازیابی کنید، موقعیت اسکرول ایدهآل نیست. اگر آنها مرورگر خود را تغییر اندازه دهند یا دستگاه خود را بچرخانند، ممکن است این مقدار به اشتباه محاسبه شود! بنابراین، بهتر است "آنچه را که آنها در حال مشاهده بودند" ذخیرهسازی کنید. شاید این به معنای استفاده از تابع getViewportOf باشد تا دقیقا بفهمید در حال حاضر چه چیزی روی صفحه نمایش قرار دارد. جزییات بستگی به این دارد که برنامه شما دقیقا چگونه کار میکند، بنابراین نمیتوانم پیشنهاد دقیقی در این مورد بدهم!
UrlChanged¶
چندین راه برای دریافت پیامهای UrlChanged وجود دارد. به تازگی دیدیم که pushUrl آنها را تولید میکند، اما فشردن دکمههای BACK و FORWARD مرورگر نیز آنها را تولید میکند. همانطور که در نکات قبلی گفتم، وقتی یک پیام LinkClicked دریافت میکنید، ممکن است دستور pushUrl بلافاصله صادر نشود.
بنابراین نکته خوب در مورد داشتن یک پیام جداگانه UrlChanged این است که مهم نیست URL چگونه یا چه زمانی تغییر کرده است. تنها چیزی که باید بدانید این است که تغییر کرده است!
در نمونه کد این صفحه فقط URL جدید را ذخیرهسازی میکنیم، اما در یک وب اپلیکیشن واقعی، شما باید URL را تجزیه و تحلیل کنید تا بفهمید چه محتوایی را نمایش دهید. در ادامه فصل، به این موضوع میپردازیم!
یادداشت
در مورد Nav.Key صحبت نکردم تا بر روی مفاهیم مهمتر تمرکز کنم. اما برای کسانی که علاقهمند هستند، اینجا توضیح میدهم!
یک کلید پیمایش یا Key برای ایجاد دستوراتی مانند pushUrl، که URL را تغییر میدهند، لازم است. فقط زمانی به یک Key دسترسی دارید که برنامه را با Browser.application ایجاد کنید. این کار تضمین میکند برنامه شما برای شناسایی تغییرات URL مجهز است. اگر مقادیر Key در انواع دیگر برنامه در دسترس بودند، برنامهنویسان بیخبر قطعا با برخی باگهای آزاردهنده مواجه میشدند و بسیاری از تکنیکها را به سختی یاد میگرفتند!
به همین دلیل، یک خط در تعریف Model برای Key داریم. این یک هزینه نسبتا کم برای کمک به جلوگیری از یک دسته مشکلات بسیار ظریف است!