Html.Lazy
¶
بسته elm/html
برای نمایش محتوا روی صفحه استفاده میشود. برای درک چگونگی بهینهسازی آن، ابتدا باید یاد بگیریم چگونه کار میکند!
DOM¶
هنگام ایجاد یک فایل HTML، بطور مستقیم چیزی شبیه به این مینویسید:
میتوانید آن را به عنوان تولید یک ساختار داده "DOM" در پسزمینه ببینید:
جعبههای سیاه نمایانگر اشیا سنگین DOM با صدها ویژگی هستند. هنگامی که هر یک از آنها تغییر کند، میتواند باعث رِندر و بازچینی پرهزینه محتوای صفحه شود.
Virtual DOM¶
هنگام ایجاد یک فایل Elm با استفاده از بسته elm/html
، بطور مستقیم چیزی شبیه به این مینویسید:
viewChairAlts : List String -> Html msg
viewChairAlts chairAlts =
div []
[ p [] [ text "Chair alternatives include:" ]
, ul [] (List.map viewAlt chairAlts)
]
viewAlt : String -> Html msg
viewAlt chairAlt =
li [] [ text chairAlt ]
میتوانید تابع viewChairAlts ["seiza","chabudai"]
را به عنوان تولید یک ساختار داده “Virtual DOM” در پسزمینه ببینید:
جعبههای سفید نمایانگر اشیا سبک DOM، با ویژگیهایی که شما مشخص کردهاید، هستند. ایجاد آنها هرگز نمیتواند باعث رِندر یا بازچینی صفحه شود. نکته این است که، در مقایسه با گرههای DOM، تخصیص منابع به این گرهها بسیار ارزانتر است!
Render¶
اگر همیشه با این گرههای مجازی در Elm کار میکنیم، چگونه به DOM که روی صفحه میبینیم تبدیل میشوند؟ وقتی یک برنامه Elm شروع میشود، به این شکل عمل میکند:
- تابع
init
برای دریافت مدل اولیه فراخوانی میشود. - تابع
view
برای دریافت گرههای مجازی اولیه فراخوانی میشود.
اکنون که گرههای مجازی را داریم، یک نسخه دقیق در DOM ایجاد میکنیم:
بسیار خوب! اما وقتی صفحه تغییر میکند چطور؟ دوبارهسازی کل DOM برای هر فِریم بهینه نیست، پس چکار کنیم؟
Diff¶
پس از اینکه DOM اولیه را داریم، به کار با گرههای مجازی ادامه میدهیم. هر زمان که Model
تغییر کند، تابع view
را اجرا میکنیم. در ادامه، گرههای مجازی حاصل را “تفاوتگذاری” میکنیم تا بفهمیم چگونه کمترین دستکاری DOM را داشته باشیم.
تصور کنید که Model
یک صندلی جایگزین جدید دریافت میکند و میخواهیم یک گره li
جدید برای آن اضافه کنیم. در پسزمینه، Elm گرههای مجازی فعلی و بعدی را برای شناسایی هرگونه تغییر، تفاوتگذاری میکند:
کامپایلر متوجه شد که یک li
سوم اضافه شده است. آن را به رنگ سبز علامتگذاری کردم. Elm اکنون دقیقا میداند که چگونه DOM را تغییر دهد تا با آن مطابقت داشته باشد. بنابراین، فقط آن li
جدید را وارد میکند:
تفاوتگذاری، امکان حداقل دستکاری DOM را فراهم میکند. اگر هیچ تفاوتی پیدا نشود، نیازی به دست زدن به DOM نداریم! بنابراین، این فرآیند به حداقل رساندن رِندرها و بازچینیهایی که باید انجام شود، کمک میکند.
اما آیا میتوانیم کار کمتری انجام دهیم؟
Html.Lazy
¶
ماژول Html.Lazy
این امکان را فراهم میکند که حتی گرههای مجازی را نیز نسازیم! ایده اصلی این ماژول، تابع lazy
است:
به مثال صندلی برگردیم، تابع viewChairAlts ["seiza","chabudai"]
را فراخوانی کردیم، اما به راحتی میتوانستیم تابع lazy viewChairAlts ["seiza","chabudai"]
را فراخوانی کنیم. این فراخوانی، یک گره “تنبل” به این شکل تخصیص میدهد:
این گره فقط یک ارجاع به تابع و آرگومانهای آن را نگه میدارد. Elm میتواند تابع و آرگومانها را با هم ترکیب کند تا در صورت نیاز کل ساختار را تولید کند، اما همیشه نیاز نیست!
یکی از ویژگیهای جالب Elm تضمین “ورودی یکسان، خروجی یکسان” برای توابع است. بنابراین هر زمان که در حین تفاوتگذاری گرههای مجازی با دو گره “تنبل” مواجه شویم، میپرسیم آیا تابع یکسان است؟ آیا آرگومانها یکسان هستند؟ اگر همه آنها یکسان باشند، میدانیم که گرههای مجازی حاصل نیز یکسان هستند! بنابراین میتوانیم بطور کامل از ساخت گرههای مجازی صرف نظر کنیم! اگر هر یک از آنها تغییر کرده باشد، میتوانیم گرههای مجازی را بسازیم و یک تفاوتگذاری عادی انجام دهیم.
یادداشت
چه زمانی دو مقدار “یکسان” هستند؟ برای بهینهسازی عملکرد، از عملگر ===
جاوااسکریپت در پسزمینه استفاده میکنیم:
- برابری ساختاری برای
Int
،Float
،String
،Char
وBool
استفاده میشود. - برابری ارجاعی برای
Record
،List
،Dictionary
وCustom Type
استفاده میشود.
برابری ساختار به این معنی است که 4
همان 4
است، مهم نیست که چگونه آن را تولید کردهاید. برابری ارجاع به این معنی است که اشارهگر واقعی در حافظه باید یکسان باشد. استفاده از برابری ارجاع همیشه ارزان و بزرگی آن O(1)
است، حتی زمانی که ساختار داده هزاران یا میلیونها ورودی داشته باشد. بنابراین، بیشتر به این مربوط میشود که اطمینان حاصل کنیم استفاده از تابع lazy
هرگز بطور تصادفی کد را کُند نمیکند. تمام بررسیها بسیار ارزان هستند!
کاربرد¶
بهترین مکان برای قرار دادن یک گره تنبل، در بالاترین قسمت برنامه یا root
است. بسیاری از برنامهها طوری طراحی شدهاند که مناطق بصری متمایزی مانند بالای صفحه، نوار کناری، محتوای اصلی و پایین صفحه داشته باشند. زمانی که توسعهدهندگان با یکی از آنها کار میکنند، به ندرت به دیگری دست میزنند. این کار، منجر به ایجاد خطوط طبیعی برای فراخوانی تابع lazy
میشود!
برای نمونه، در پیادهسازی TodoMVC، تابع view
به این شکل تعریف شده است:
view : Model -> Html Msg
view model =
div
[ class "todomvc-wrapper"
, style "visibility" "hidden"
]
[ section
[ class "todoapp" ]
[ lazy viewInput model.field
, lazy2 viewEntries model.visibility model.entries
, lazy2 viewControls model.visibility model.entries
]
, infoFooter
]
توجه کنید که ورودی کاربر، سطرها و کنترلها همه در گرههای تنبل جداگانهای قرار دارند. بنابراین میتوان هر تعداد کاراکتر در ورودی تایپ کرد بدون اینکه هرگز گرههای مجازی برای سطرها یا کنترلها ساخته شود. آنها در حال تغییر نیستند! در نتیجه، اولین نکته این است که سعی کنید از گرههای تنبل در بالاترین قسمت برنامه استفاده کنید.
همچنین، استفاده از تابع lazy
در فهرست طولانی اقلام، میتواند مفید باشد. در برنامه TodoMVC، همه چیز درباره افزودن ورودیها به فهرست کارها است. میتوانید به راحتی صدها ورودی داشته باشید، اما آنها به ندرت تغییر میکنند. این بخش، یک کاندیدای عالی برای تنبلی است! با تغییر تابع viewEntry entry
به lazy viewEntry entry
میتوانیم از تخصیص منابعی که به ندرت مفید هستند، صرف نظر کنیم. در نتیجه، دومین نکته این است که سعی کنید از گرههای تنبل در ساختارهای تکراری استفاده کنید که هر مورد آن به ندرت تغییر میکند.
خلاصه¶
دستکاری DOM بسیار پر هزینهتر از چیزی است که در یک رابط کاربری عادی اتفاق میافتد. شما میتوانید هر کاری که میخواهید با ساختارهای داده پیچیده انجام دهید، اما در نهایت تنها چیزی که اهمیت دارد این است که چقدر بطور موفقیتآمیز از تابع lazy
استفاده میکنید.
در قسمت بعد، یک تکنیک برای استفاده بهتر از تابع lazy
میآموزیم!