اجعل واجهة الويب الأمامية موثوقة باستخدام Elm
نشرت: 2022-03-11كم مرة حاولت تصحيح أخطاء الواجهة الأمامية للويب ووجدت نفسك متشابكًا في كود يتعامل مع سلاسل معقدة من الأحداث؟
هل سبق لك أن حاولت إعادة تشكيل التعليمات البرمجية لواجهة مستخدم تتعامل مع الكثير من المكونات التي تم إنشاؤها باستخدام jQuery أو Backbone.js أو غيرها من أطر JavaScript الشائعة؟
من أكثر الأمور إيلامًا في هذه السيناريوهات محاولة متابعة التسلسلات المتعددة غير المحددة للأحداث وتوقع كل هذه السلوكيات وإصلاحها. مجرد كابوس!
لقد بحثت دائمًا عن طرق للهروب من هذا الجانب الجهنمية لتطوير الواجهة الأمامية للويب. عملت Backbone.js بشكل جيد بالنسبة لي في هذا الصدد من خلال إعطاء الواجهات الأمامية للويب البنية التي فقدتها لفترة طويلة. ولكن بالنظر إلى الإسهاب الذي يتطلبه القيام ببعض الأشياء التافهة ، لم يكن أفضل بكثير.
ثم قابلت إلم.
Elm هي لغة وظيفية مكتوبة بشكل ثابت تعتمد على لغة برمجة Haskell ، ولكن مع مواصفات أبسط. يوزع المترجم (الذي تم إنشاؤه أيضًا باستخدام Haskell) كود Elm ويجمعها في JavaScript.
تم إنشاء Elm في الأصل لتطوير الواجهة الأمامية ، لكن مهندسي البرمجيات وجدوا طرقًا لاستخدامه في البرمجة من جانب الخادم أيضًا.
تقدم هذه المقالة نظرة عامة على كيفية قيام Elm بتغيير الطريقة التي نفكر بها في تطوير الواجهة الأمامية للويب ومقدمة لأساسيات لغة البرمجة الوظيفية هذه. في هذا البرنامج التعليمي ، سنطور تطبيقًا بسيطًا يشبه عربة التسوق باستخدام Elm.
لماذا علم؟
يعد Elm بالكثير من المزايا ، معظمها مفيد للغاية في تحقيق بنية واجهة ويب نظيفة. تقدم Elm مزايا أداء عرض HTML أفضل على الأطر الشائعة الأخرى (حتى React.js). علاوة على ذلك ، يسمح Elm للمطورين بكتابة التعليمات البرمجية ، والتي في الممارسة العملية ، لا تنتج معظم استثناءات وقت التشغيل التي تصيب اللغات المكتوبة ديناميكيًا مثل JavaScript.
يستنتج المترجم الأنواع تلقائيًا وينبعث منها أخطاء ودية ، مما يجعل المطور على دراية بأي مشكلة محتملة قبل وقت التشغيل.
يحتوي NoRedInk على 36000 سطر من Elm ، وبعد أكثر من عام من الإنتاج ، لم ينتج بعد استثناء واحد لوقت التشغيل. [مصدر]
لا تحتاج إلى تحويل تطبيق JavaScript الحالي بالكامل حتى تتمكن من تجربة Elm. من خلال قابلية التشغيل البيني الرائعة مع JavaScript ، يمكنك حتى أن تأخذ جزءًا صغيرًا فقط من التطبيق الحالي الخاص بك ، وتعيد كتابته في Elm.
تمتلك Elm أيضًا وثائق ممتازة لا تمنحك وصفًا شاملاً لما تقدمه فحسب ، بل تمنحك أيضًا دليلًا مناسبًا لبناء واجهة الويب الأمامية بعد The Elm Architecture - وهو شيء رائع للوحدة وإعادة استخدام الكود والاختبار .
لنصنع عربة بسيطة
لنبدأ بمقتطف قصير جدًا من كود Elm:
import List exposing (..) cart = [] item product quantity = { product = product, qty = quantity } product name price = { name = name, price = price } add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [item product 1] else cart subtotal cart = -- we want to calculate cart subtotal sum (map (\item -> item.product.price * toFloat item.qty) cart)أي نص يسبقه
--هو تعليق في علم.
نحن هنا نحدد سلة التسوق على أنها قائمة بالعناصر ، حيث يكون كل عنصر عبارة عن سجل به قيمتين (المنتج الذي يتوافق معه والكمية). كل منتج عبارة عن سجل باسم وسعر.
تتضمن إضافة منتج إلى سلة التسوق التحقق مما إذا كان العنصر موجودًا بالفعل في سلة التسوق.
إذا كان الأمر كذلك ، فإننا لا نفعل شيئًا ؛ بخلاف ذلك ، نضيف المنتج إلى سلة التسوق كعنصر جديد. نتحقق مما إذا كان المنتج موجودًا بالفعل في سلة التسوق عن طريق تصفية القائمة ، ومطابقة كل عنصر مع المنتج ، والتحقق مما إذا كانت القائمة المصفاة الناتجة فارغة.
لحساب الإجمالي الفرعي ، نكرر العناصر الموجودة في سلة التسوق ، ونجد كمية المنتج المقابلة والسعر ، ونلخصها كلها.
هذا هو أضيق الحدود مثل عربة ويمكن الحصول على الوظائف ذات الصلة. سنبدأ بهذا الكود ، ونحسنه خطوة بخطوة مما يجعله مكون ويب كاملًا ، أو برنامجًا وفقًا لشروط Elm.
لنبدأ بإضافة أنواع إلى المعرفات المختلفة في برنامجنا. Elm قادر على استنتاج الأنواع من تلقاء نفسه ولكن لتحقيق أقصى استفادة من Elm والمترجم الخاص به ، يوصى بالإشارة إلى الأنواع صراحةً.
module Cart1 exposing ( Cart, Item, Product , add, subtotal , itemSubtotal ) -- This is module and its API definition {-| We build an easy shopping cart. @docs Cart, Item, Product, add, subtotal, itemSubtotal -} import List exposing (..) -- we need list manipulation functions {-| Cart is a list of items. -} type alias Cart = List Item {-| Item is a record of product and quantity. -} type alias Item = { product : Product, qty : Int } {-| Product is a record with name and price -} type alias Product = { name : String, price : Float } {-| We want to add stuff to a cart. This is a function definition, it takes a cart, a product to add and returns new cart -} add : Cart -> Product -> Cart {-| This is an implementation of the 'add' function. Just append product item to the cart if there is no such product in the cart listed. Do nothing if the product exists. -} add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [Item product 1] else cart {-| I need to calculate cart subtotal. The function takes a cart and returns float. -} subtotal : Cart -> Float {-| It's easy -- just sum subtotal of all items. -} subtotal cart = sum (map itemSubtotal cart) {-| Item subtotal takes item and return the subtotal float. -} itemSubtotal : Item -> Float {-| Subtotal is based on product's price and quantity. -} itemSubtotal item = item.product.price * toFloat item.qtyباستخدام التعليقات التوضيحية للنوع ، يمكن للمجمع الآن اكتشاف المشكلات التي كان من الممكن أن تؤدي إلى استثناءات وقت التشغيل.
ومع ذلك ، فإن Elm لا يتوقف عند هذا الحد. يوجه Elm Architecture المطورين من خلال نمط بسيط لهيكلة واجهات الويب الأمامية الخاصة بهم ، ويقوم بذلك من خلال المفاهيم التي يعرفها معظم المطورين بالفعل:
- النموذج: النماذج تحمل حالة البرنامج.
- طريقة العرض: العرض هو تمثيل مرئي للدولة.
- التحديث: التحديث هو وسيلة لتغيير الحالة.
إذا كنت تفكر في جزء من الكود الخاص بك يتعامل مع التحديثات باعتباره وحدة التحكم ، فلديك شيء مشابه جدًا لنموذج Model-View-Controller (MVC) القديم الجيد.
نظرًا لأن Elm هي لغة برمجة وظيفية خالصة ، فإن جميع البيانات غير قابلة للتغيير ، مما يعني أنه لا يمكن تغيير النموذج. بدلاً من ذلك ، يمكننا إنشاء نموذج جديد بناءً على النموذج السابق ، وهو ما نقوم به من خلال وظائف التحديث.
لماذا هذا عظيم جدا؟
مع البيانات غير القابلة للتغيير ، لم يعد للوظائف آثار جانبية. هذا يفتح عالمًا من الاحتمالات ، بما في ذلك مصحح أخطاء السفر عبر الزمن Elm ، والذي سنناقشه قريبًا.
يتم تقديم العروض في كل مرة يتطلب تغيير في النموذج تغييرًا في العرض ، وسيكون لدينا دائمًا نفس النتيجة لنفس البيانات في النموذج - بالطريقة نفسها التي تُرجع فيها دالة نقية دائمًا نفس النتيجة لـ نفس حجج الإدخال.
ابدأ بالوظيفة الرئيسية
دعنا نمضي قدمًا وننفذ عرض HTML لتطبيق عربة التسوق الخاص بنا.
إذا كنت معتادًا على React ، فهذا شيء أنا متأكد من أنك ستقدره: لدى Elm حزمة لتعريف عناصر HTML فقط. يتيح لك ذلك تنفيذ وجهة نظرك باستخدام Elm ، دون الحاجة إلى الاعتماد على لغات النماذج الخارجية.
تتوفر أغلفة عناصر HTML ضمن حزمة Html :
import Html exposing (Html, button, table, caption, thead, tbody, tfoot, tr, td, th, text, section, p, h1)تبدأ جميع برامج Elm بتنفيذ الوظيفة الرئيسية:
type alias Stock = List Product type alias Model = { cart : Cart, stock : Stock } main = Html.beginnerProgram { model = Model [] [ Product "Bicycle" 100.50 , Product "Rocket" 15.36 , Product "Biscuit" 21.15 ] , view = view , update = update }هنا ، تقوم الوظيفة الرئيسية بتهيئة برنامج Elm مع بعض الطرز ، وطريقة عرض ، ووظيفة تحديث. لقد حددنا أنواعًا قليلة من المنتجات وأسعارها. للتبسيط ، نفترض أن لدينا عددًا غير محدود من المنتجات.
وظيفة تحديث بسيطة
وظيفة التحديث هي المكان الذي يأتي فيه تطبيقنا إلى الحياة.
يأخذ رسالة ويقوم بتحديث الحالة بناءً على محتوى الرسالة. نحددها كدالة تأخذ معلمتين (رسالة والنموذج الحالي) وتعيد نموذجًا جديدًا:
type Msg = Add Product update : Msg -> Model -> Model update msg model = case msg of Add product -> { model | cart = add model.cart product } في الوقت الحالي ، نتعامل مع حالة واحدة عندما تكون الرسالة Add product ، حيث نسمي طريقة add في cart مع product .
ستنمو وظيفة التحديث مع نمو تعقيد برنامج Elm.
تنفيذ وظيفة العرض
بعد ذلك ، نحدد طريقة عرض عربة التسوق الخاصة بنا.
العرض هو وظيفة تترجم النموذج إلى تمثيل HTML الخاص به. ومع ذلك ، فهي ليست مجرد لغة HTML ثابتة. منشئ HTML قادر على إرسال الرسائل مرة أخرى إلى التطبيق بناءً على تفاعلات وأحداث المستخدم المختلفة.
view : Model -> Html Msg view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart ] stockView : Stock -> Html Msg stockView stock = table [] [ caption [] [ h1 [] [ text "Stock" ] ] , thead [] [ tr [] [ th [align "left", width 100] [ text "Name" ] , th [align "right", width 100] [ text "Price" ] , th [width 100] [] ] ] , tbody [] (map stockProductView stock) ] stockProductView : Product -> Html Msg stockProductView product = tr [] [ td [] [ text product.name ] , td [align "right"] [ text ("\t$" ++ toString product.price) ] , td [] [ button [ onClick (Add product) ] [ text "Add to Cart" ] ] ] توفر حزمة Html أغلفة لجميع العناصر شائعة الاستخدام كوظائف ذات أسماء مألوفة (على سبيل المثال ، يولد section الوظيفة عنصر <section> ).
تُنشئ وظيفة style ، وهي جزء من حزمة Html.Attributes ، كائنًا يمكن تمريره إلى وظيفة section لتعيين سمة النمط على العنصر الناتج.
من الأفضل تقسيم العرض إلى وظائف منفصلة لتحسين إمكانية إعادة الاستخدام.
لإبقاء الأمور بسيطة ، قمنا بتضمين CSS وبعض سمات التخطيط مباشرة في كود العرض الخاص بنا. ومع ذلك ، توجد مكتبات تعمل على تبسيط عملية تصميم عناصر HTML من كود Elm.
لاحظ button بالقرب من نهاية المقتطف وكيف قمنا بربط الرسالة " Add product " بحدث النقر على الزر.
يعتني Elm بإنشاء كل التعليمات البرمجية اللازمة لربط دالة رد الاتصال بالحدث الفعلي وإنشاء واستدعاء وظيفة التحديث بالمعلمات ذات الصلة.
أخيرًا ، نحتاج إلى تنفيذ الجزء الأخير من وجهة نظرنا:
cartView : Cart -> Html Msg cartView cart = if isEmpty cart then p [] [ text "Cart is empty" ] else table [] [ caption [] [ h1 [] [ text "Cart" ]] , thead [] [ tr [] [ th [ align "left", width 100 ] [ text "Name" ] , th [ align "right", width 100 ] [ text "Price" ] , th [ align "center", width 50 ] [ text "Qty" ] , th [ align "right", width 100 ] [ text "Subtotal" ] ] ] , tbody [] (map cartProductView cart) , tfoot [] [ tr [style [("font-weight", "bold")]] [ td [ align "right", colspan 4 ] [ text ("$" ++ toString (subtotal cart)) ] ] ] ] cartProductView : Item -> Html Msg cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text ("$" ++ toString item.product.price) ] , td [ align "center" ] [ text (toString item.qty) ] , td [ align "right" ] [ text ("$" ++ toString (itemSubtotal item)) ] ] لقد حددنا هنا الجزء الآخر من وجهة نظرنا حيث نعرض محتويات سلة التسوق الخاصة بنا. على الرغم من أن وظيفة العرض لا ترسل أي رسالة ، إلا أنها لا تزال بحاجة إلى نوع الإرجاع Html Msg للتأهل كطريقة عرض.
لا يسرد العرض محتويات سلة التسوق فحسب ، بل يحسب ويعرض الإجمالي الفرعي بناءً على محتويات سلة التسوق.
يمكنك العثور على الكود الكامل لبرنامج Elm هنا.
إذا كنت ستقوم بتشغيل برنامج Elm الآن ، فسترى شيئًا مثل هذا:
كيف يعمل هذا كله؟
يبدأ برنامجنا بحالة فارغة إلى حد ما من الوظيفة main - عربة فارغة بها عدد قليل من المنتجات المشفرة.
في كل مرة يتم النقر فوق الزر "إضافة إلى عربة التسوق" ، يتم إرسال رسالة إلى وظيفة التحديث ، والتي تقوم بعد ذلك بتحديث عربة التسوق وفقًا لذلك وإنشاء نموذج جديد. عندما يتم تحديث النموذج ، يتم استدعاء وظائف العرض بواسطة Elm لإعادة إنشاء شجرة HTML.
نظرًا لأن Elm يستخدم نهج DOM الظاهري ، على غرار نهج React ، لا يتم إجراء التغييرات على واجهة المستخدم إلا عند الضرورة ، مما يضمن أداءً سريعًا.

ليس مجرد مدقق نوع
يتم كتابة Elm بشكل ثابت ، ولكن يمكن للمجمع التحقق من أكثر بكثير من الأنواع فقط.
دعونا نجري تغييرًا على نوع Msg الخاص بنا ونرى كيف يتفاعل المترجم مع ذلك:
type Msg = Add Product | ChangeQty Product Stringلقد حددنا نوعًا آخر من الرسائل - شيء من شأنه تغيير كمية المنتج في سلة التسوق. ومع ذلك ، ستؤدي محاولة تشغيل البرنامج مرة أخرى دون معالجة هذه الرسالة في وظيفة التحديث إلى إرسال الخطأ التالي:
نحو عربة أكثر فاعلية
لاحظ أنه في القسم السابق استخدمنا سلسلة كنوع لقيمة الكمية. هذا لأن القيمة ستأتي من عنصر <input> والذي سيكون من نوع سلسلة.
دعنا نضيف تغيير وظيفة جديد changeQty إلى وحدة Cart . من الأفضل دائمًا الاحتفاظ بالتنفيذ داخل الوحدة لتتمكن من تغييرها لاحقًا إذا لزم الأمر دون تغيير واجهة برمجة تطبيقات الوحدة.
{-| Change quantity of the product in the cart. Look at the result of the function. It uses Result type. The Result type has two parameters: for bad and for good result. So the result will be Error "msg" or a Cart with updated product quantity. -} changeQty : Cart -> Product -> Int -> Result String Cart {-| If the quantity parameter is zero the product will be removed completely from the cart. If the quantity parameter is greater then zero the quantity of the product will be updated. Otherwise (qty < 0) the error will be returned. -} changeQty cart product qty = if qty == 0 then Ok (filter (\item -> item.product /= product) cart) else if qty > 0 then Result.Ok (map (\item -> if item.product == product then { item | qty = qty } else item) cart) else Result.Err ("Wrong negative quantity used: " ++ (toString qty)) لا ينبغي أن نفترض أي افتراضات حول كيفية استخدام الوظيفة. يمكننا أن نكون على ثقة من أن المعلمة qty ستحتوي على Int لكن القيمة يمكن أن تكون أي شيء. لذلك نتحقق من القيمة ونبلغ عن خطأ عندما يكون غير صالح.
نقوم أيضًا بتحديث وظيفة update الخاصة بنا وفقًا لذلك:
update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart } Err msg -> model -- do nothing, the wrong input Err msg -> model -- do nothing, the wrong quantity نقوم بتحويل معلمة quantity السلسلة من الرسالة إلى رقم قبل استخدامها. في حالة احتواء السلسلة على رقم غير صالح ، فإننا نبلغ عنها كخطأ.
هنا ، نحافظ على النموذج دون تغيير عند حدوث خطأ. بدلاً من ذلك ، يمكننا فقط تحديث النموذج بطريقة للإبلاغ عن الخطأ كرسالة في طريقة العرض ليراها المستخدم:
type alias Model = { cart : Cart, stock : Stock, error : Maybe String } main = Html.beginnerProgram { model = Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) , view = view , update = update } update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart, error = Nothing } Err msg -> { model | error = Just msg } Err msg -> { model | error = Just msg } نستخدم النوع Maybe String لسمة الخطأ في نموذجنا. ربما يكون نوعًا آخر يمكن أن يحمل Nothing أو قيمة من نوع معين.
بعد تحديث وظيفة العرض على النحو التالي:
view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart , errorView model.error ] errorView : Maybe String -> Html Msg errorView error = case error of Just msg -> p [style [("color", "red")]] [ text msg ] Nothing -> p [] []يجب أن ترى هذا:
قد تؤدي محاولة إدخال قيمة غير رقمية (مثل "1 أ") إلى ظهور رسالة خطأ كما هو موضح في لقطة الشاشة أعلاه.
عالم الحزم
تمتلك Elm مستودعًا خاصًا بها من الحزم مفتوحة المصدر. مع مدير الحزم لشركة Elm ، يصبح من السهل الاستفادة من مجموعة الحزم هذه. على الرغم من أن حجم المستودع لا يمكن مقارنته ببعض لغات البرمجة الأخرى الناضجة مثل Python أو PHP ، فإن مجتمع Elm يعمل بجد لتنفيذ المزيد من الحزم كل يوم.
لاحظ كيف أن الخانات العشرية في الأسعار المعروضة من وجهة نظرنا غير متسقة؟
دعنا نستبدل استخدامنا البسيط لـ toString بشيء أفضل من المستودع: numeral-elm.
cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text (formatPrice item.product.price) ] , td [ align "center" ] [ input [ value (toString item.qty) , onInput (ChangeQty item.product) , size 3 --, type' "number" ] [] ] , td [ align "right" ] [ text (formatPrice (itemSubtotal item)) ] ] formatPrice : Float -> String formatPrice price = format "$0,0.00" price نحن نستخدم وظيفة format من الحزمة الرقمية هنا. سيؤدي هذا إلى تنسيق الأرقام بطريقة نقوم بها عادة بتنسيق العملات:
100.5 -> $100.50 15.36 -> $15.36 21.15 -> $21.15توليد التوثيق التلقائي
عند نشر حزمة إلى مستودع Elm ، يتم إنشاء الوثائق تلقائيًا بناءً على التعليقات الموجودة في الكود. يمكنك رؤيتها أثناء العمل عن طريق التحقق من الوثائق الخاصة بوحدة عربة التسوق هنا. تم إنشاء كل هذه من التعليقات الموجودة في هذا الملف: Cart.elm.
مصحح أخطاء حقيقي للواجهة الأمامية
يتم اكتشاف معظم المشكلات الواضحة والإبلاغ عنها بواسطة المترجم نفسه. ومع ذلك ، لا يوجد تطبيق آمن من الأخطاء المنطقية.
نظرًا لأن جميع البيانات في Elm غير قابلة للتغيير وكل شيء يحدث من خلال الرسائل التي تم تمريرها إلى وظيفة التحديث ، يمكن تمثيل التدفق الكامل لبرنامج Elm كسلسلة من تغييرات النموذج. بالنسبة إلى مصحح الأخطاء ، فإن Elm تشبه تمامًا لعبة إستراتيجية تعتمد على الأدوار. يسمح هذا لمصحح الأخطاء بأداء بعض الأعمال البطولية المذهلة ، مثل السفر عبر الزمن. يمكن أن يتحرك ذهابًا وإيابًا خلال تدفق البرنامج عن طريق القفز بين تغييرات النموذج المختلفة التي حدثت أثناء عمر البرنامج.
يمكنك معرفة المزيد عن مصحح الأخطاء هنا.
التعامل مع النهاية الخلفية
لذا ، كما تقول ، قمنا ببناء لعبة جميلة ، ولكن هل يمكن استخدام Elm في شيء خطير؟ إطلاقا.
دعنا نربط الواجهة الأمامية لعربة التسوق الخاصة بنا ببعض الواجهة الخلفية غير المتزامنة. لجعله ممتعًا ، سننفذ شيئًا مميزًا. لنفترض أننا نريد فحص جميع عربات التسوق ومحتوياتها في الوقت الفعلي. في الحياة الواقعية ، يمكننا استخدام هذا النهج لجلب بعض إمكانات التسويق / المبيعات الإضافية إلى متجرنا أو سوقنا عبر الإنترنت ، أو تقديم بعض الاقتراحات للمستخدم ، أو تقدير موارد المخزون المطلوبة ، وأكثر من ذلك بكثير.
لذلك ، نقوم بتخزين العربة على جانب العميل ونعلم الخادم أيضًا بكل عربة في الوقت الفعلي.
لتبسيط الأمور ، سنقوم بتنفيذ الخلفية الخاصة بنا باستخدام Python. يمكنك العثور على الكود الكامل للخلفية هنا.
إنه خادم ويب بسيط يستخدم WebSocket ويتتبع محتويات عربة التسوق في الذاكرة. لتبسيط الأمور ، سنعرض عربة التسوق لأي شخص آخر في نفس الصفحة. يمكن تنفيذ ذلك بسهولة في صفحة منفصلة أو حتى كبرنامج Elm منفصل. في الوقت الحالي ، سيتمكن كل مستخدم من رؤية ملخص عربات المستخدمين الآخرين.
مع وجود الواجهة الخلفية في مكانها ، سنحتاج الآن إلى تحديث تطبيق Elm الخاص بنا لإرسال واستقبال تحديثات سلة التسوق إلى الخادم. سنستخدم JSON لترميز حمولاتنا ، والتي يتمتع Elm بدعم ممتاز لها.
CartEncoder.elm
سنقوم بتنفيذ برنامج تشفير لتحويل نموذج بيانات Elm الخاص بنا إلى تمثيل سلسلة JSON. لذلك ، نحتاج إلى استخدام مكتبة Json.Encode.
module CartEncoder exposing (cart) import Cart2 exposing (Cart, Item, Product) import List exposing (map) import Json.Encode exposing (..) product : Product -> Value product product = object [ ("name", string product.name) , ("price", float product.price) ] item : Item -> Value item item = object [ ("product", product item.product) , ("qty", int item.qty) ] cart : Cart -> Value cart cart = list (map item cart) توفر المكتبة بعض الوظائف (مثل string ، int ، float ، object ، إلخ) التي تأخذ كائنات Elm وتحولها إلى سلاسل JSON مشفرة.
CartDecoder.elm
يعد تنفيذ وحدة فك التشفير أكثر تعقيدًا نظرًا لأن جميع بيانات Elm لها أنواع ونحتاج إلى تحديد قيمة JSON التي يجب تحويلها إلى أي نوع:
module CartDecoder exposing (cart) import Cart2 exposing (Cart, Item, Product) -- decoding for Cart import Json.Decode exposing (..) -- will decode cart from string cart : Decoder (Cart) cart = list item -- decoder for cart is a list of items item : Decoder (Item) item = object2 Item -- decoder for item is an object with two properties: ("product" := product) -- 1) "product" of product ("qty" := int) -- 2) "qty" of int product : Decoder (Product) product = object2 Product -- decoder for product also an object with two properties: ("name" := string) -- 1) "name" ("price" := float) -- 2) "price"تطبيق علم المحدث
نظرًا لأن كود Elm النهائي أطول قليلاً ، يمكنك العثور عليه هنا. فيما يلي ملخص للتغييرات التي تم إجراؤها على تطبيق الواجهة الأمامية:
لقد قمنا بتغليف وظيفة update الأصلية الخاصة بنا بوظيفة ترسل التغييرات إلى محتويات عربة التسوق إلى النهاية الخلفية في كل مرة يتم فيها تحديث سلة التسوق:
updateOnServer msg model = let (newModel, have_to_send) = update msg model in case have_to_send of True -> -- send updated cart to server (!) newModel [ WebSocket.send server (encode 0 (CartEncoder.cart newModel.cart)) ] False -> -- do nothing (newModel, Cmd.none) لقد أضفنا أيضًا نوع رسالة إضافيًا من ConsumerCarts String لتلقي التحديثات من الخادم وتحديث النموذج المحلي وفقًا لذلك.
تم تحديث طريقة العرض لتقديم ملخص عربات الآخرين باستخدام وظيفة consumersCartsView .
تم إنشاء اتصال WebSocket للاشتراك في النهاية الخلفية للاستماع إلى التغييرات التي تطرأ على عربات الآخرين.
subscriptions : Model -> Sub Msg subscriptions model = WebSocket.listen server ConsumerCarts server = "ws://127.0.0.1:8765" لقد قمنا أيضًا بتحديث وظيفتنا الرئيسية. نستخدم الآن Html.program مع معلمات init subscriptions إضافية. يحدد init النموذج الأولي للبرنامج ويحدد subscription قائمة بالاشتراكات.
الاشتراك هو وسيلة يمكننا من خلالها إخبار Elm بالاستماع إلى التغييرات على قنوات معينة وإعادة توجيه هذه الرسائل إلى وظيفة update .
main = Html.program { init = init , view = view , update = updateOnServer , subscriptions = subscriptions } init = ( Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) [] -- consumer carts list is empty , Cmd.none)أخيرًا ، تعاملنا مع طريقة فك شفرة رسالة ConsumerCarts التي نتلقاها من الخادم. هذا يضمن أن البيانات التي نتلقاها من مصدر خارجي لن تعطل التطبيق.
ConsumerCarts message -> case decodeString (Json.Decode.list CartDecoder.cart) message of Ok carts -> ({ model | consumer_carts = carts }, False) Err msg -> ({ model | error = Just msg, consumer_carts = [] }, False)حافظ على واجهاتك الأمامية عاقلة
علم مختلف. يتطلب من المطور التفكير بشكل مختلف.
أي شخص قادم من عالم JavaScript واللغات المماثلة سيجد نفسه يحاول تعلم طريقة Elm في فعل الأشياء.
في النهاية ، تقدم Elm شيئًا تكافح الأطر الأخرى - حتى الأكثر شيوعًا منها - غالبًا من أجل القيام به. وهي توفر وسيلة لإنشاء تطبيقات أمامية قوية دون التشابك في كود مطول ضخم.
يتخلص Elm أيضًا من العديد من الصعوبات التي تطرحها JavaScript من خلال الجمع بين مترجم ذكي ومصحح أخطاء قوي.
Elm هو ما يتوق إليه مطورو الواجهة الأمامية لفترة طويلة. الآن بعد أن شاهدته قيد التنفيذ ، خذها بنفسك ، وجني الفوائد من خلال بناء مشروع الويب التالي في Elm.
