กี่ครั้งแล้วที่คุณพยายามแก้ไขจุดบกพร่องของเว็บส่วนหน้าและพบว่าตัวเองยุ่งอยู่กับโค้ดที่เกี่ยวข้องกับเหตุการณ์ที่ซับซ้อน
คุณเคยพยายาม refactor code สำหรับ UI ที่เกี่ยวข้องกับส่วนประกอบจำนวนมากที่สร้างด้วย jQuery, Backbone.js หรือ JavaScript framework ยอดนิยมอื่น ๆ หรือไม่?
สิ่งที่เจ็บปวดที่สุดอย่างหนึ่งเกี่ยวกับสถานการณ์เหล่านี้คือการพยายามทำตามลำดับเหตุการณ์ที่ไม่แน่นอนหลาย ๆ อย่างและคาดการณ์และแก้ไขพฤติกรรมเหล่านี้ทั้งหมด แค่ฝันร้าย!
ฉันมองหาวิธีที่จะหลีกหนีการพัฒนาส่วนหน้าเว็บที่เลวร้ายนี้มาโดยตลอด Backbone.js ทำงานได้ดีสำหรับฉันในเรื่องนี้โดยการให้โครงสร้างส่วนหน้าของเว็บที่ขาดหายไปเป็นเวลานาน แต่ด้วยความฟุ่มเฟื่อยที่จำเป็นในการทำสิ่งที่ไม่สำคัญที่สุดมันก็ไม่ได้ดีไปกว่านี้
แล้วฉันก็ได้พบ เอล์ม .
Elm เป็นภาษาที่ใช้งานได้แบบคงที่โดยใช้ภาษาโปรแกรม Haskell แต่มีไฟล์ ข้อกำหนดที่ง่ายกว่า . คอมไพเลอร์ (สร้างโดยใช้ Haskell) แยกวิเคราะห์โค้ด Elm และรวบรวมเป็น JavaScript
เดิมที Elm ถูกสร้างขึ้นเพื่อการพัฒนาส่วนหน้า แต่วิศวกรซอฟต์แวร์ได้ค้นพบวิธีที่จะใช้มันสำหรับการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์เช่นกัน
บทความนี้แสดงภาพรวมว่า Elm สามารถเปลี่ยนวิธีที่เราคิดเกี่ยวกับการพัฒนาส่วนหน้าของเว็บได้อย่างไรและการแนะนำพื้นฐานของภาษาโปรแกรมที่ใช้งานได้นี้ ในบทช่วยสอนนี้เราจะพัฒนาแอปพลิเคชันที่เรียบง่ายเหมือนตะกร้าสินค้าโดยใช้ Elm
Elm สัญญาว่าจะได้รับประโยชน์มากมายซึ่งส่วนใหญ่มีประโยชน์อย่างมากในการบรรลุสถาปัตยกรรมฟรอนต์เอนด์ของเว็บที่สะอาด ข้อเสนอของ Elm ข้อดีด้านประสิทธิภาพการแสดงผล HTML ที่ดีขึ้น เหนือเฟรมเวิร์กยอดนิยมอื่น ๆ (แม้กระทั่ง React.js) ยิ่งไปกว่านั้น Elm ยังอนุญาตให้นักพัฒนาเขียนโค้ดซึ่งในทางปฏิบัติแล้วจะไม่สร้างข้อยกเว้นรันไทม์ส่วนใหญ่ที่ทำให้เกิดปัญหากับภาษาที่พิมพ์แบบไดนามิกเช่น JavaScript
คอมไพเลอร์จะสรุปประเภทโดยอัตโนมัติและปล่อยข้อผิดพลาดที่เป็นมิตรทำให้ผู้พัฒนาทราบ ปัญหาที่อาจเกิดขึ้น ก่อนรันไทม์
NoRedInk มี Elm 36,000 ไลน์และหลังจากใช้งานไปนานกว่าหนึ่งปีก็ยังไม่ได้สร้างข้อยกเว้นรันไทม์แม้แต่รายการเดียว [ ที่มา ]
คุณไม่จำเป็นต้องแปลงแอปพลิเคชัน JavaScript ที่มีอยู่ทั้งหมดเพื่อให้คุณสามารถทดลองใช้ Elm ได้ ด้วยความสามารถในการทำงานร่วมกันที่ยอดเยี่ยมกับ JavaScript คุณสามารถใช้เพียงส่วนเล็ก ๆ ของแอปพลิเคชันที่มีอยู่ของคุณและเขียนซ้ำใน Elm
Elm ยังมี เอกสารที่ยอดเยี่ยม ที่ไม่เพียง แต่ให้คำอธิบายอย่างละเอียดเกี่ยวกับสิ่งที่นำเสนอ แต่ยังให้คำแนะนำที่เหมาะสมในการสร้างส่วนหน้าเว็บต่อไปนี้ สถาปัตยกรรม Elm - สิ่งที่ยอดเยี่ยมสำหรับการแยกส่วนการใช้โค้ดซ้ำและการทดสอบ
ให้เราเริ่มต้นด้วยโค้ดสั้น ๆ ของ 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 .
ในที่นี้เรากำลังกำหนดรถเข็นให้เป็นรายการสินค้าโดยทุกรายการเป็นบันทึกที่มีค่าสองค่า (สินค้าที่สอดคล้องกับและปริมาณ) ผลิตภัณฑ์แต่ละรายการเป็นบันทึกที่มีชื่อและราคา
การเพิ่มสินค้าลงในรถเข็นเกี่ยวข้องกับการตรวจสอบว่ามีสินค้าอยู่ในรถเข็นหรือไม่
ถ้าเป็นเช่นนั้นเราไม่ทำอะไรเลย มิฉะนั้นเราจะเพิ่มสินค้าลงในรถเข็นเป็นสินค้าใหม่ เราตรวจสอบว่ามีสินค้าอยู่ในรถเข็นหรือไม่โดยการกรองรายการจับคู่สินค้าแต่ละรายการกับสินค้าและตรวจสอบว่ารายการที่กรองแล้วว่างเปล่า
concurrency ในการเขียนโปรแกรมคืออะไร
ในการคำนวณผลรวมย่อยเราจะวนซ้ำรายการในรถเข็นค้นหาปริมาณสินค้าและราคาที่สอดคล้องกันและสรุปทั้งหมด
นี่เป็นแบบเรียบง่ายเหมือนรถเข็นและฟังก์ชันที่เกี่ยวข้องจะได้รับ เราจะเริ่มต้นด้วยโค้ดนี้และปรับปรุงทีละขั้นตอนเพื่อให้เป็นส่วนประกอบของเว็บที่สมบูรณ์หรือเป็นโปรแกรมในข้อกำหนดของ 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 - 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 - 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 จะแนะนำนักพัฒนาผ่านรูปแบบง่ายๆในการจัดโครงสร้างส่วนหน้าของเว็บและทำผ่านแนวคิดที่นักพัฒนาส่วนใหญ่คุ้นเคยอยู่แล้ว:
หากคุณคิดว่าส่วนหนึ่งของรหัสของคุณเกี่ยวข้องกับ การปรับปรุง ในฐานะผู้ควบคุมคุณจะมีบางอย่างที่คล้ายกับกระบวนทัศน์ 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 -> 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 (' $' ++ toString product.price) ] , td [] [ button [ onClick (Add product) ] [ text 'Add to Cart' ] ] ]
Html
แพคเกจมีการห่อสำหรับองค์ประกอบที่ใช้ทั่วไปทั้งหมดเป็นฟังก์ชันที่มีชื่อที่คุ้นเคย (เช่นฟังก์ชัน 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 จะดำเนินการเมื่อจำเป็นเท่านั้นเพื่อให้มั่นใจถึงประสิทธิภาพที่รวดเร็ว
ประสิทธิภาพของโหนด js เทียบกับ java
Elm ถูกพิมพ์แบบคงที่ แต่คอมไพเลอร์สามารถตรวจสอบได้มากกว่าแค่ประเภท
มาเปลี่ยนแปลง Msg
ของเรากัน พิมพ์และดูว่าคอมไพเลอร์ตอบสนองต่อสิ่งนั้นอย่างไร:
type Msg = Add Product | ChangeQty Product String
เราได้กำหนดข้อความประเภทอื่นซึ่งจะทำให้ปริมาณสินค้าในรถเข็นเปลี่ยนไป อย่างไรก็ตามการพยายามรันโปรแกรมอีกครั้งโดยไม่จัดการข้อความนี้ในฟังก์ชันอัพเดตจะทำให้เกิดข้อผิดพลาดต่อไปนี้:
โปรดทราบว่าในส่วนก่อนหน้านี้เราใช้สตริงเป็นประเภทสำหรับค่าปริมาณ เนื่องจากค่าจะมาจาก anelement ซึ่งจะเป็นสตริงประเภท
มาเพิ่มฟังก์ชันใหม่ 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 item.product /= product) cart) else if qty > 0 then Result.Ok (map (item -> if item.product == product then item else item) cart) else Result.Err ('Wrong negative quantity used: ' ++ (toString qty))
เราไม่ควรตั้งสมมติฐานว่าจะใช้ฟังก์ชันนี้อย่างไร เรามั่นใจได้ว่าพารามิเตอร์ qty
จะมี Int
แต่มูลค่าอาจเป็นอะไรก็ได้ เราจึงตรวจสอบค่าและ รายงานข้อผิดพลาด เมื่อมันไม่ถูกต้อง
นอกจากนี้เรายังอัปเดต update
ของเรา ทำงานตาม:
update msg model = case msg of Add product -> cart = add model.cart product ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> model 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 -> cart = add model.cart product ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> model Err msg -> error = Just msg Err msg -> model
เราใช้ประเภท 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 มันเป็นเรื่องง่ายที่จะใช้ประโยชน์จากกลุ่มแพ็คเกจนี้ แม้ว่าขนาดของพื้นที่เก็บข้อมูลจะไม่สามารถเทียบเคียงได้กับภาษาการเขียนโปรแกรมสำหรับผู้ใหญ่อื่น ๆ เช่น Python หรือ PHP แต่ชุมชน Elm กำลังทำงานอย่างหนักเพื่อใช้งานแพ็กเกจเพิ่มเติมทุกวัน
สังเกตว่าตำแหน่งทศนิยมในราคาที่แสดงในมุมมองของเราไม่สอดคล้องกันอย่างไร?
มาแทนที่การใช้ toString
แบบไร้เดียงสาของเรา ด้วยสิ่งที่ดีกว่าจากที่เก็บ: ตัวเลขเอล์ม .
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 'ทำให้ Web Front-end เชื่อถือได้ด้วย Elm
กี่ครั้งแล้วที่คุณพยายามแก้ไขจุดบกพร่องของเว็บส่วนหน้าและพบว่าตัวเองยุ่งอยู่กับโค้ดที่เกี่ยวข้องกับเหตุการณ์ที่ซับซ้อน
คุณเคยพยายาม refactor code สำหรับ UI ที่เกี่ยวข้องกับส่วนประกอบจำนวนมากที่สร้างด้วย jQuery, Backbone.js หรือ JavaScript framework ยอดนิยมอื่น ๆ หรือไม่?
สิ่งที่เจ็บปวดที่สุดอย่างหนึ่งเกี่ยวกับสถานการณ์เหล่านี้คือการพยายามทำตามลำดับเหตุการณ์ที่ไม่แน่นอนหลาย ๆ อย่างและคาดการณ์และแก้ไขพฤติกรรมเหล่านี้ทั้งหมด แค่ฝันร้าย!
ฉันมองหาวิธีที่จะหลีกหนีการพัฒนาส่วนหน้าเว็บที่เลวร้ายนี้มาโดยตลอด Backbone.js ทำงานได้ดีสำหรับฉันในเรื่องนี้โดยการให้โครงสร้างส่วนหน้าของเว็บที่ขาดหายไปเป็นเวลานาน แต่ด้วยความฟุ่มเฟื่อยที่จำเป็นในการทำสิ่งที่ไม่สำคัญที่สุดมันก็ไม่ได้ดีไปกว่านี้
แล้วฉันก็ได้พบ เอล์ม .
Elm เป็นภาษาที่ใช้งานได้แบบคงที่โดยใช้ภาษาโปรแกรม Haskell แต่มีไฟล์ ข้อกำหนดที่ง่ายกว่า . คอมไพเลอร์ (สร้างโดยใช้ Haskell) แยกวิเคราะห์โค้ด Elm และรวบรวมเป็น JavaScript
เดิมที Elm ถูกสร้างขึ้นเพื่อการพัฒนาส่วนหน้า แต่วิศวกรซอฟต์แวร์ได้ค้นพบวิธีที่จะใช้มันสำหรับการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์เช่นกัน
บทความนี้แสดงภาพรวมว่า Elm สามารถเปลี่ยนวิธีที่เราคิดเกี่ยวกับการพัฒนาส่วนหน้าของเว็บได้อย่างไรและการแนะนำพื้นฐานของภาษาโปรแกรมที่ใช้งานได้นี้ ในบทช่วยสอนนี้เราจะพัฒนาแอปพลิเคชันที่เรียบง่ายเหมือนตะกร้าสินค้าโดยใช้ Elm
Elm สัญญาว่าจะได้รับประโยชน์มากมายซึ่งส่วนใหญ่มีประโยชน์อย่างมากในการบรรลุสถาปัตยกรรมฟรอนต์เอนด์ของเว็บที่สะอาด ข้อเสนอของ Elm ข้อดีด้านประสิทธิภาพการแสดงผล HTML ที่ดีขึ้น เหนือเฟรมเวิร์กยอดนิยมอื่น ๆ (แม้กระทั่ง React.js) ยิ่งไปกว่านั้น Elm ยังอนุญาตให้นักพัฒนาเขียนโค้ดซึ่งในทางปฏิบัติแล้วจะไม่สร้างข้อยกเว้นรันไทม์ส่วนใหญ่ที่ทำให้เกิดปัญหากับภาษาที่พิมพ์แบบไดนามิกเช่น JavaScript
คอมไพเลอร์จะสรุปประเภทโดยอัตโนมัติและปล่อยข้อผิดพลาดที่เป็นมิตรทำให้ผู้พัฒนาทราบ ปัญหาที่อาจเกิดขึ้น ก่อนรันไทม์
NoRedInk มี Elm 36,000 ไลน์และหลังจากใช้งานไปนานกว่าหนึ่งปีก็ยังไม่ได้สร้างข้อยกเว้นรันไทม์แม้แต่รายการเดียว [ ที่มา ]
คุณไม่จำเป็นต้องแปลงแอปพลิเคชัน JavaScript ที่มีอยู่ทั้งหมดเพื่อให้คุณสามารถทดลองใช้ Elm ได้ ด้วยความสามารถในการทำงานร่วมกันที่ยอดเยี่ยมกับ JavaScript คุณสามารถใช้เพียงส่วนเล็ก ๆ ของแอปพลิเคชันที่มีอยู่ของคุณและเขียนซ้ำใน Elm
Elm ยังมี เอกสารที่ยอดเยี่ยม ที่ไม่เพียง แต่ให้คำอธิบายอย่างละเอียดเกี่ยวกับสิ่งที่นำเสนอ แต่ยังให้คำแนะนำที่เหมาะสมในการสร้างส่วนหน้าเว็บต่อไปนี้ สถาปัตยกรรม Elm - สิ่งที่ยอดเยี่ยมสำหรับการแยกส่วนการใช้โค้ดซ้ำและการทดสอบ
ให้เราเริ่มต้นด้วยโค้ดสั้น ๆ ของ 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 - 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 - 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 จะแนะนำนักพัฒนาผ่านรูปแบบง่ายๆในการจัดโครงสร้างส่วนหน้าของเว็บและทำผ่านแนวคิดที่นักพัฒนาส่วนใหญ่คุ้นเคยอยู่แล้ว:
หากคุณคิดว่าส่วนหนึ่งของรหัสของคุณเกี่ยวข้องกับ การปรับปรุง ในฐานะผู้ควบคุมคุณจะมีบางอย่างที่คล้ายกับกระบวนทัศน์ 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 -> 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 (' $' ++ toString product.price) ] , td [] [ button [ onClick (Add product) ] [ text 'Add to Cart' ] ] ]
Html
แพคเกจมีการห่อสำหรับองค์ประกอบที่ใช้ทั่วไปทั้งหมดเป็นฟังก์ชันที่มีชื่อที่คุ้นเคย (เช่นฟังก์ชัน 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
เราได้กำหนดข้อความประเภทอื่นซึ่งจะทำให้ปริมาณสินค้าในรถเข็นเปลี่ยนไป อย่างไรก็ตามการพยายามรันโปรแกรมอีกครั้งโดยไม่จัดการข้อความนี้ในฟังก์ชันอัพเดตจะทำให้เกิดข้อผิดพลาดต่อไปนี้:
โปรดทราบว่าในส่วนก่อนหน้านี้เราใช้สตริงเป็นประเภทสำหรับค่าปริมาณ เนื่องจากค่าจะมาจาก anelement ซึ่งจะเป็นสตริงประเภท
มาเพิ่มฟังก์ชันใหม่ 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 item.product /= product) cart) else if qty > 0 then Result.Ok (map (item -> if item.product == product then item else item) cart) else Result.Err ('Wrong negative quantity used: ' ++ (toString qty))
เราไม่ควรตั้งสมมติฐานว่าจะใช้ฟังก์ชันนี้อย่างไร เรามั่นใจได้ว่าพารามิเตอร์ qty
จะมี Int
แต่มูลค่าอาจเป็นอะไรก็ได้ เราจึงตรวจสอบค่าและ รายงานข้อผิดพลาด เมื่อมันไม่ถูกต้อง
นอกจากนี้เรายังอัปเดต update
ของเรา ทำงานตาม:
update msg model = case msg of Add product -> cart = add model.cart product ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> model 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 -> cart = add model.cart product ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> model Err msg -> error = Just msg Err msg -> model
เราใช้ประเภท 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 มันเป็นเรื่องง่ายที่จะใช้ประโยชน์จากกลุ่มแพ็คเกจนี้ แม้ว่าขนาดของพื้นที่เก็บข้อมูลจะไม่สามารถเทียบเคียงได้กับภาษาการเขียนโปรแกรมสำหรับผู้ใหญ่อื่น ๆ เช่น Python หรือ PHP แต่ชุมชน Elm กำลังทำงานอย่างหนักเพื่อใช้งานแพ็กเกจเพิ่มเติมทุกวัน
สังเกตว่าตำแหน่งทศนิยมในราคาที่แสดงในมุมมองของเราไม่สอดคล้องกันอย่างไร?
มาแทนที่การใช้ toString
แบบไร้เดียงสาของเรา ด้วยสิ่งที่ดีกว่าจากที่เก็บ: ตัวเลขเอล์ม .
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 ก็เหมือนกับเกมวางแผนผลัดกันเล่น สิ่งนี้ช่วยให้ดีบักเกอร์สามารถแสดงผลงานที่น่าทึ่งได้เช่นการเดินทางข้ามเวลา มันสามารถเลื่อนไปมาตามโฟลว์ของโปรแกรมได้โดยการกระโดดไปมาระหว่างการเปลี่ยนแปลงโมเดลต่างๆที่เกิดขึ้นในช่วงอายุของโปรแกรม
คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับดีบักเกอร์ ที่นี่ .
คุณว่าเราได้สร้างของเล่นดีๆขึ้นมา แต่เอล์มสามารถนำไปใช้กับสิ่งที่ร้ายแรงได้หรือไม่? อย่างแน่นอน
มาเชื่อมต่อส่วนหน้ารถเข็นของเรากับส่วนหลังแบบอะซิงโครนัส เพื่อให้น่าสนใจเราจะใช้สิ่งพิเศษ สมมติว่าเราต้องการตรวจสอบรถเข็นและเนื้อหาทั้งหมดแบบเรียลไทม์ ในชีวิตจริงเราสามารถใช้แนวทางนี้เพื่อเพิ่มความสามารถด้านการตลาด / การขายให้กับร้านค้าออนไลน์หรือตลาดของเราหรือให้คำแนะนำแก่ผู้ใช้หรือประเมินทรัพยากรสต็อกที่ต้องการและอื่น ๆ อีกมากมาย
ดังนั้นเราจึงจัดเก็บรถเข็นในฝั่งลูกค้าและแจ้งให้เซิร์ฟเวอร์ทราบเกี่ยวกับรถเข็นแต่ละรายการแบบเรียลไทม์
เพื่อให้สิ่งต่างๆง่ายขึ้นเราจะใช้ส่วนหลังของเราโดยใช้ Python คุณสามารถค้นหารหัสเต็มสำหรับส่วนหลังได้ ที่นี่ .
เป็นเว็บเซิร์ฟเวอร์ธรรมดาที่ใช้ WebSocket และติดตามเนื้อหาของรถเข็นในหน่วยความจำ เพื่อให้สิ่งต่างๆง่ายขึ้นเราจะแสดงรถเข็นของทุกคนในหน้าเดียวกัน สิ่งนี้สามารถนำไปใช้งานได้อย่างง่ายดายในหน้าแยกต่างหากหรือแม้แต่โปรแกรม Elm แยก ในตอนนี้ผู้ใช้ทุกคนจะสามารถดูสรุปรถเข็นของผู้ใช้รายอื่นได้
เมื่อใช้แบ็คเอนด์แล้วตอนนี้เราจำเป็นต้องอัปเดตแอป Elm ของเราเพื่อส่งและรับการอัปเดตรถเข็นไปยังเซิร์ฟเวอร์ เราจะใช้ JSON เพื่อเข้ารหัสข้อมูลของเราซึ่ง Elm มีการสนับสนุนที่ดีเยี่ยม
เราจะใช้ตัวเข้ารหัสเพื่อแปลงโมเดลข้อมูล Elm ของเราเป็นการแสดงสตริง JSON เพื่อที่เราจะต้องใช้ไฟล์ Json เข้ารหัสไลบรารี .
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
การติดตั้งตัวถอดรหัส ค่อนข้างยุ่งยากกว่าเนื่องจากข้อมูล 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 -> ( consumer_carts = carts , False) Err msg -> ( model , False)
เอล์มแตกต่างกัน ผู้พัฒนาต้องคิดแตกต่างออกไป
ใครก็ตามที่มาจากขอบเขตของ JavaScript และภาษาที่คล้ายกันจะพบว่าตัวเองพยายามเรียนรู้วิธีการทำสิ่งต่างๆของ Elm
ในท้ายที่สุด Elm ก็นำเสนอบางสิ่งที่เฟรมเวิร์กอื่น ๆ แม้แต่เฟรมเวิร์กที่เป็นที่นิยมมากที่สุดก็มักจะพยายามทำ กล่าวคือเป็นวิธีการสร้างแอปพลิเคชันฟรอนต์เอนด์ที่มีประสิทธิภาพโดยไม่ต้องยุ่งเกี่ยวกับโค้ด verbose ขนาดใหญ่
เอล์มยังแยกไฟล์ ปัญหาที่ JavaScript ก่อให้เกิด โดยการรวมคอมไพเลอร์อัจฉริยะกับดีบักเกอร์ที่ทรงพลัง
Elm เป็นสิ่งที่นักพัฒนาส่วนหน้าใฝ่ฝันมานาน ตอนนี้คุณได้เห็นการดำเนินการแล้วลองหมุนตัวเองและเก็บเกี่ยวผลประโยชน์ด้วยการสร้างไฟล์ โครงการเว็บถัดไป ใน Elm
เรากำลังใช้ format
ฟังก์ชันจากแพ็คเกจตัวเลขที่นี่ สิ่งนี้จะจัดรูปแบบตัวเลขด้วยวิธีที่เราจัดรูปแบบสกุลเงินโดยทั่วไป:
100.5 -> 0.50 15.36 -> .36 21.15 -> .15
เมื่อเผยแพร่แพ็กเกจไปยังที่เก็บ Elm เอกสารจะถูกสร้างขึ้นโดยอัตโนมัติตามความคิดเห็นในโค้ด คุณสามารถดูการทำงานได้โดยดูเอกสารประกอบสำหรับโมดูลรถเข็นของเรา ที่นี่ . สิ่งเหล่านี้สร้างขึ้นจากความคิดเห็นที่เห็นในไฟล์นี้: Cart.elm .
ปัญหาที่ชัดเจนส่วนใหญ่ถูกตรวจพบและรายงานโดยคอมไพเลอร์เอง อย่างไรก็ตามไม่มีแอปพลิเคชันใดที่ปลอดภัยจากข้อผิดพลาดทางตรรกะ
เนื่องจากข้อมูลทั้งหมดใน Elm ไม่เปลี่ยนรูปและทุกอย่างเกิดขึ้นผ่านข้อความที่ส่งไปยังฟังก์ชันการอัปเดตโฟลว์ทั้งหมดของโปรแกรม Elm จึงสามารถแสดงเป็นชุดของการเปลี่ยนแปลงโมเดลได้ สำหรับดีบักเกอร์ Elm ก็เหมือนกับเกมวางแผนผลัดกันเล่น สิ่งนี้ช่วยให้ดีบักเกอร์สามารถแสดงผลงานที่น่าทึ่งได้เช่นการเดินทางข้ามเวลา มันสามารถเลื่อนไปมาตามโฟลว์ของโปรแกรมได้โดยการกระโดดไปมาระหว่างการเปลี่ยนแปลงโมเดลต่างๆที่เกิดขึ้นในช่วงอายุของโปรแกรม
คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับดีบักเกอร์ ที่นี่ .
คุณว่าเราได้สร้างของเล่นดีๆขึ้นมา แต่เอล์มสามารถนำไปใช้กับสิ่งที่ร้ายแรงได้หรือไม่? อย่างแน่นอน
มาเชื่อมต่อส่วนหน้ารถเข็นของเรากับส่วนหลังแบบอะซิงโครนัส เพื่อให้น่าสนใจเราจะใช้สิ่งพิเศษ สมมติว่าเราต้องการตรวจสอบรถเข็นและเนื้อหาทั้งหมดแบบเรียลไทม์ ในชีวิตจริงเราสามารถใช้แนวทางนี้เพื่อเพิ่มความสามารถด้านการตลาด / การขายให้กับร้านค้าออนไลน์หรือตลาดของเราหรือให้คำแนะนำแก่ผู้ใช้หรือประเมินทรัพยากรสต็อกที่ต้องการและอื่น ๆ อีกมากมาย
ดังนั้นเราจึงจัดเก็บรถเข็นในฝั่งลูกค้าและแจ้งให้เซิร์ฟเวอร์ทราบเกี่ยวกับรถเข็นแต่ละรายการแบบเรียลไทม์
เพื่อให้สิ่งต่างๆง่ายขึ้นเราจะใช้ส่วนหลังของเราโดยใช้ Python คุณสามารถค้นหารหัสเต็มสำหรับส่วนหลังได้ ที่นี่ .
เป็นเว็บเซิร์ฟเวอร์ธรรมดาที่ใช้ WebSocket และติดตามเนื้อหาของรถเข็นในหน่วยความจำ เพื่อให้สิ่งต่างๆง่ายขึ้นเราจะแสดงรถเข็นของทุกคนในหน้าเดียวกัน สิ่งนี้สามารถนำไปใช้งานได้อย่างง่ายดายในหน้าแยกต่างหากหรือแม้แต่โปรแกรม Elm แยก ในตอนนี้ผู้ใช้ทุกคนจะสามารถดูสรุปรถเข็นของผู้ใช้รายอื่นได้
เมื่อใช้แบ็คเอนด์แล้วตอนนี้เราจำเป็นต้องอัปเดตแอป Elm ของเราเพื่อส่งและรับการอัปเดตรถเข็นไปยังเซิร์ฟเวอร์ เราจะใช้ JSON เพื่อเข้ารหัสข้อมูลของเราซึ่ง Elm มีการสนับสนุนที่ดีเยี่ยม
เราจะใช้ตัวเข้ารหัสเพื่อแปลงโมเดลข้อมูล Elm ของเราเป็นการแสดงสตริง JSON เพื่อที่เราจะต้องใช้ไฟล์ Json เข้ารหัสไลบรารี .
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
การติดตั้งตัวถอดรหัส ค่อนข้างยุ่งยากกว่าเนื่องจากข้อมูล 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 -> ( consumer_carts = carts , False) Err msg -> ( model , False)
เอล์มแตกต่างกัน ผู้พัฒนาต้องคิดแตกต่างออกไป
ใครก็ตามที่มาจากขอบเขตของ JavaScript และภาษาที่คล้ายกันจะพบว่าตัวเองพยายามเรียนรู้วิธีการทำสิ่งต่างๆของ Elm
ในท้ายที่สุด Elm ก็นำเสนอบางสิ่งที่เฟรมเวิร์กอื่น ๆ แม้แต่เฟรมเวิร์กที่เป็นที่นิยมมากที่สุดก็มักจะพยายามทำ กล่าวคือเป็นวิธีการสร้างแอปพลิเคชันฟรอนต์เอนด์ที่มีประสิทธิภาพโดยไม่ต้องยุ่งเกี่ยวกับโค้ด verbose ขนาดใหญ่
วิธีการใช้แมชชีนเลิร์นนิง
เอล์มยังแยกไฟล์ ปัญหาที่ JavaScript ก่อให้เกิด โดยการรวมคอมไพเลอร์อัจฉริยะกับดีบักเกอร์ที่ทรงพลัง
Elm เป็นสิ่งที่นักพัฒนาส่วนหน้าใฝ่ฝันมานาน ตอนนี้คุณได้เห็นการดำเนินการแล้วลองหมุนตัวเองและเก็บเกี่ยวผลประโยชน์ด้วยการสร้างไฟล์ โครงการเว็บถัดไป ใน Elm