ทำให้เว็บฟรอนต์เอนด์ของคุณเชื่อถือได้ด้วยElm
เผยแพร่แล้ว: 2022-03-11กี่ครั้งแล้วที่คุณพยายามดีบักส่วนหน้าของเว็บและพบว่าตัวเองพันกันในโค้ดที่เกี่ยวข้องกับกลุ่มเหตุการณ์ที่ซับซ้อน?
คุณเคยพยายามปรับโครงสร้างโค้ดสำหรับ UI ที่จัดการกับส่วนประกอบจำนวนมากที่สร้างด้วย jQuery, Backbone.js หรือเฟรมเวิร์ก JavaScript ยอดนิยมอื่นๆ หรือไม่
สิ่งที่เจ็บปวดที่สุดอย่างหนึ่งเกี่ยวกับสถานการณ์เหล่านี้คือการพยายามทำตามลำดับเหตุการณ์ที่ไม่แน่นอนหลายครั้ง และคาดการณ์และแก้ไขพฤติกรรมเหล่านี้ทั้งหมด แค่ฝันร้าย!
ฉันมองหาวิธีที่จะหลีกหนีจากความชั่วร้ายของการพัฒนาเว็บฟรอนท์เอนด์อยู่เสมอ Backbone.js ทำงานได้ดีสำหรับฉันในเรื่องนี้โดยให้โครงสร้างเว็บส่วนหน้าที่พวกเขาหายไปเป็นเวลานาน แต่ด้วยการใช้คำฟุ่มเฟือยที่ต้องใช้ในการทำสิ่งเล็กน้อยที่สุด ก็ไม่ได้กลายเป็นว่าดีไปกว่านี้มากนัก
แล้วฉันก็ได้พบกับเอล์ม
Elm เป็นภาษาการทำงานที่พิมพ์แบบสแตติกตามภาษาการเขียนโปรแกรม Haskell แต่มีข้อกำหนดที่ง่ายกว่า คอมไพเลอร์ (สร้างโดยใช้ Haskell ด้วย) แยกวิเคราะห์โค้ด Elm และคอมไพล์เป็น JavaScript
เดิมที Elm ถูกสร้างขึ้นสำหรับการพัฒนา front-end แต่วิศวกรซอฟต์แวร์ได้พบวิธีที่จะใช้มันสำหรับการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์เช่นกัน
บทความนี้ให้ภาพรวมว่า Elm สามารถเปลี่ยนแปลงวิธีที่เราคิดเกี่ยวกับการพัฒนาเว็บฟรอนท์เอนด์ได้อย่างไร และบทนำเกี่ยวกับพื้นฐานของภาษาการเขียนโปรแกรมเชิงฟังก์ชันนี้ ในบทช่วยสอนนี้ เราจะพัฒนาแอปพลิเคชันที่เหมือนตะกร้าสินค้าอย่างง่ายโดยใช้ Elm
ทำไมต้องเอล์ม?
Elm ให้คำมั่นสัญญาถึงข้อดีมากมาย ซึ่งส่วนใหญ่มีประโยชน์อย่างมากในการบรรลุสถาปัตยกรรมส่วนหน้าของเว็บที่สะอาดตา Elm นำเสนอข้อได้เปรียบด้านประสิทธิภาพการเรนเดอร์ HTML ที่ดีกว่าเฟรมเวิร์กยอดนิยมอื่นๆ (แม้แต่ React.js) นอกจากนี้ Elm ยังช่วยให้นักพัฒนาสามารถเขียนโค้ดได้ ซึ่งในทางปฏิบัติ จะไม่สร้างข้อยกเว้นรันไทม์ส่วนใหญ่ที่ทำให้เกิดปัญหากับภาษาที่พิมพ์แบบไดนามิก เช่น JavaScript
คอมไพเลอร์อนุมานประเภทโดยอัตโนมัติและปล่อยข้อผิดพลาดที่เป็นมิตร ทำให้นักพัฒนาทราบถึงปัญหาที่อาจเกิดขึ้นก่อนรันไทม์
NoRedInk มี Elm 36,000 ไลน์ และหลังจากการผลิตมานานกว่าหนึ่งปี ก็ยังไม่มีข้อยกเว้นรันไทม์แม้แต่ครั้งเดียว [แหล่งที่มา]
คุณไม่จำเป็นต้องแปลงแอปพลิเคชัน 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 สามารถอนุมานประเภทได้ด้วยตัวเอง แต่เพื่อให้เกิดประโยชน์สูงสุดจาก 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 จะแนะนำนักพัฒนาซอฟต์แวร์ผ่านรูปแบบง่ายๆ สำหรับการจัดโครงสร้างส่วนหน้าของเว็บ และดำเนินการผ่านแนวคิดที่นักพัฒนาส่วนใหญ่คุ้นเคยอยู่แล้ว:
- รุ่น: โมเดลถือสถานะของโปรแกรม
- มุมมอง: มุมมองคือการแสดงภาพของรัฐ
- อัปเดต: อัปเดตเป็นวิธีการเปลี่ยนสถานะ
หากคุณคิดว่าส่วนหนึ่งของโค้ดที่เกี่ยวข้องกับ การอัปเดต เป็นคอนโทรลเลอร์ แสดงว่าคุณมีบางอย่างที่คล้ายกับกระบวนทัศน์ Model-View-Controller (MVC) แบบเก่าที่ดี
เนื่องจาก Elm เป็นภาษาการเขียนโปรแกรมที่ใช้งานได้จริง ข้อมูลทั้งหมดจึงไม่เปลี่ยนรูปแบบ ซึ่งหมายความว่าโมเดลไม่สามารถเปลี่ยนแปลงได้ แต่เราสามารถสร้างแบบจำลองใหม่โดยอิงจากรุ่นก่อนหน้า ซึ่งเราทำผ่านฟังก์ชันการอัพเดท
ทำไมมันเยี่ยมเยี่ยงนี้
ด้วยข้อมูลที่ไม่เปลี่ยนรูป ฟังก์ชันต่างๆ จะไม่มีผลข้างเคียงอีกต่อไป นี่เป็นการเปิดโลกแห่งความเป็นไปได้ ซึ่งรวมถึงโปรแกรมแก้ไขจุดบกพร่องการเดินทางข้ามเวลาของ Elm ซึ่งเราจะพูดถึงในไม่ช้า
มุมมองจะแสดงผลทุกครั้งที่มีการเปลี่ยนแปลงในแบบจำลองจำเป็นต้องมีการเปลี่ยนแปลงในมุมมอง และเราจะมีผลลัพธ์เดียวกันสำหรับข้อมูลเดียวกันในแบบจำลอง - ในลักษณะเดียวกับที่ฟังก์ชันบริสุทธิ์จะส่งคืนผลลัพธ์เดียวกันเสมอสำหรับ อาร์กิวเมนต์อินพุตเดียวกัน
เริ่มต้นด้วยฟังก์ชั่นหลัก
มาเริ่มใช้งานมุมมอง HTML สำหรับแอปพลิเคชันรถเข็นของเรากัน
หากคุณคุ้นเคยกับ React นี่คือสิ่งที่ผมมั่นใจว่าคุณจะประทับใจ: Elm มีแพ็คเกจสำหรับกำหนดองค์ประกอบ HTML เท่านั้น สิ่งนี้ทำให้คุณสามารถใช้มุมมองของคุณโดยใช้ Elm โดยไม่ต้องพึ่งพาภาษาเทมเพลตภายนอก
Wrappers สำหรับองค์ประกอบ 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
จัดเตรียม wrappers สำหรับองค์ประกอบที่ใช้ทั่วไปทั้งหมดเป็นฟังก์ชันที่มีชื่อที่คุ้นเคย (เช่น 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 ใช้วิธี Virtual DOM ซึ่งคล้ายกับ React การเปลี่ยนแปลง UI จะดำเนินการเมื่อจำเป็นเท่านั้น จึงมั่นใจได้ถึงประสิทธิภาพที่รวดเร็ว
ไม่ใช่แค่ตัวตรวจสอบประเภท
Elm ถูกพิมพ์แบบสแตติก แต่คอมไพเลอร์สามารถตรวจสอบได้มากกว่าแค่ประเภท
มาทำการเปลี่ยนแปลงประเภท Msg
ของเราและดูว่าคอมไพเลอร์ตอบสนองอย่างไร:
type Msg = Add Product | ChangeQty Product String
เราได้กำหนดข้อความประเภทอื่น - สิ่งที่จะเปลี่ยนปริมาณของผลิตภัณฑ์ในรถเข็น อย่างไรก็ตาม การพยายามเรียกใช้โปรแกรมอีกครั้งโดยไม่จัดการข้อความนี้ในฟังก์ชันการอัปเดตจะทำให้เกิดข้อผิดพลาดต่อไปนี้:
สู่รถเข็นที่ใช้งานได้ดีกว่า
โปรดทราบว่าในส่วนก่อนหน้านี้ เราใช้สตริงเป็นประเภทของค่าปริมาณ เนื่องจากค่าจะมาจากองค์ประกอบ <input>
ซึ่งจะเป็นประเภทสตริง
มาเพิ่มฟังก์ชันใหม่ changeQty
ให้กับโมดูล Cart
กันเถอะ จะดีกว่าเสมอที่จะใช้งานภายในโมดูลเพื่อให้สามารถเปลี่ยนแปลงได้ในภายหลังหากจำเป็นโดยไม่ต้องเปลี่ยน API ของโมดูล
{-| 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 }
เราใช้ type 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 [] []
คุณควรเห็นสิ่งนี้:
การพยายามป้อนค่าที่ไม่ใช่ตัวเลข (เช่น “1a”) จะส่งผลให้เกิดข้อความแสดงข้อผิดพลาดดังที่แสดงในภาพหน้าจอด้านบน
โลกแห่งแพ็คเกจ
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
True Debugger สำหรับ Front-end
ปัญหาที่ชัดเจนที่สุดจะถูกตรวจพบและรายงานโดยคอมไพเลอร์เอง อย่างไรก็ตาม ไม่มีแอปพลิเคชันใดปลอดภัยจากข้อผิดพลาดทางตรรกะ
เนื่องจากข้อมูลทั้งหมดใน 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
เนื่องจากรหัส 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 คือสิ่งที่นักพัฒนา front-end ใฝ่ฝันมานาน เมื่อคุณได้เห็นการใช้งานจริงแล้ว ลองใช้มันด้วยตัวคุณเอง และเก็บเกี่ยวผลประโยชน์ด้วยการสร้างโครงการเว็บถัดไปของคุณใน Elm