Webフロントエンドをデバッグしようとして、複雑な一連のイベントを処理するコードに巻き込まれたことは何回ありますか?
jQuery、Backbone.js、またはその他の一般的なJavaScriptフレームワークで構築された多くのコンポーネントを処理するUIのコードをリファクタリングしようとしたことがありますか?
これらのシナリオで最も苦痛なことの1つは、イベントの複数の不確定なシーケンスを追跡し、これらすべての動作を予測して修正しようとすることです。単に悪夢!
私は常に、Webフロントエンド開発のこの地獄のような側面から逃れる方法を探してきました。 Backbone.jsは、Webフロントエンドに長い間欠けていた構造を与えることで、この点でうまく機能しました。しかし、最も些細なことを行うために必要な冗長性を考えると、それほど優れているとは言えませんでした。
それから私は会った エルム 。
Elmは、Haskellプログラミング言語に基づいた静的に型付けされた関数型言語ですが、 よりシンプルな仕様 。コンパイラー(これもHaskellを使用して構築されています)はElmコードを解析し、JavaScriptにコンパイルします。
Elmは元々フロントエンド開発用に構築されましたが、ソフトウェアエンジニアは、サーバー側のプログラミングにも使用する方法を見つけました。
この記事では、ElmがWebフロントエンド開発の考え方をどのように変えることができるかについての概要と、この関数型プログラミング言語の基本について説明します。このチュートリアルでは、Elmを使用して簡単なショッピングカートのようなアプリケーションを開発します。
Elmには多くの利点があり、そのほとんどはクリーンなWebフロントエンドアーキテクチャを実現するのに非常に役立ちます。エルムは提供しています より優れたHTMLレンダリングパフォーマンスの利点 他の一般的なフレームワーク(React.jsでさえ)よりも。さらに、Elmを使用すると、開発者はコードを記述できます。コードは、実際には、JavaScriptなどの動的に型指定された言語を悩ますほとんどのランタイム例外を生成しません。
コンパイラーはタイプを自動的に推測し、わかりやすいエラーを発行して、開発者に認識させます 潜在的な問題 実行前。
NoRedInkには36,000行のElmがあり、1年以上生産された後も、ランタイム例外は1つも発生していません。 [ ソース ]
Elmを試すためだけに、既存のJavaScriptアプリケーション全体を変換する必要はありません。 JavaScriptとの優れた相互運用性により、既存のアプリケーションのごく一部を取得して、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のコメント 。
ここでは、カートをアイテムのリストとして定義しています。ここで、すべてのアイテムは2つの値(対応する製品と数量)を持つレコードです。各製品は、名前と価格が記載されたレコードです。
カートに商品を追加するには、その商品がカートにすでに存在するかどうかを確認する必要があります。
もしそうなら、私たちは何もしません。それ以外の場合は、商品を新しいアイテムとしてカートに追加します。リストをフィルタリングし、各アイテムを商品と照合し、結果のフィルタリングされたリストが空であるかどうかを確認することにより、商品がカートにすでに存在するかどうかを確認します。
小計を計算するために、カート内のアイテムを繰り返し処理し、対応する製品の数量と価格を見つけて、すべてを合計します。
これはカートとその関連機能が得ることができるのと同じくらいミニマルです。このコードから始めて、段階的に改善し、完全なWebコンポーネント、または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アーキテクチャは、Webフロントエンドを構造化するための単純なパターンを通じて開発者をガイドし、ほとんどの開発者がすでに精通している概念を通じてそれを行います。
あなたがあなたのコードの一部を扱っていると思うなら 更新 コントローラーとして、古き良きModel-View-Controller(MVC)パラダイムに非常によく似たものがあります。
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プログラムは、main関数を実行することから始まります。
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 }
ここで、main関数は、いくつかのモデル、ビュー、および更新関数を使用してElmプログラムを初期化します。いくつかの種類の製品とその価格を定義しました。簡単にするために、製品の数に制限はないと仮定しています。
次のうちどれが設計の原則です
更新機能は、私たちのアプリケーションが活気づくところです。
メッセージを受け取り、メッセージの内容に基づいて状態を更新します。これを、2つのパラメーター(メッセージと現在のモデル)を受け取り、新しいモデルを返す関数として定義します。
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といくつかのレイアウト属性をビューコードに直接埋め込みました。ただし、を合理化するライブラリが存在します スタイリングのプロセス ElmコードからのHTML要素。
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はReactと同様の仮想DOMアプローチを使用しているため、UIの変更は必要な場合にのみ実行され、迅速なパフォーマンスが保証されます。
Elmは静的に型付けされていますが、コンパイラーは型だけでなく多くのことをチェックできます。
Msg
に変更を加えましょう入力して、コンパイラがそれにどのように反応するかを確認します。
type Msg = Add Product | ChangeQty Product String
別の種類のメッセージを定義しました。カート内の商品の数量を変更するものです。ただし、更新関数でこのメッセージを処理せずにプログラムを再実行しようとすると、次のエラーが発生します。
前のセクションでは、数量値のタイプとして文字列を使用したことに注意してください。これは、値が文字列型の要素から取得されるためです。
新しい関数を追加しましょう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
の単純な使用を置き換えましょうリポジトリからより良いもので: 数字-エルム 。
macosはどの言語で書かれていますか
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 'ElmでWebフロントエンドの信頼性を高める
Webフロントエンドをデバッグしようとして、複雑な一連のイベントを処理するコードに巻き込まれたことは何回ありますか?
jQuery、Backbone.js、またはその他の一般的なJavaScriptフレームワークで構築された多くのコンポーネントを処理するUIのコードをリファクタリングしようとしたことがありますか?
これらのシナリオで最も苦痛なことの1つは、イベントの複数の不確定なシーケンスを追跡し、これらすべての動作を予測して修正しようとすることです。単に悪夢!
私は常に、Webフロントエンド開発のこの地獄のような側面から逃れる方法を探してきました。 Backbone.jsは、Webフロントエンドに長い間欠けていた構造を与えることで、この点でうまく機能しました。しかし、最も些細なことを行うために必要な冗長性を考えると、それほど優れているとは言えませんでした。
それから私は会った エルム 。
Elmは、Haskellプログラミング言語に基づいた静的に型付けされた関数型言語ですが、 よりシンプルな仕様 。コンパイラー(これもHaskellを使用して構築されています)はElmコードを解析し、JavaScriptにコンパイルします。
Elmは元々フロントエンド開発用に構築されましたが、ソフトウェアエンジニアは、サーバー側のプログラミングにも使用する方法を見つけました。
この記事では、ElmがWebフロントエンド開発の考え方をどのように変えることができるかについての概要と、この関数型プログラミング言語の基本について説明します。このチュートリアルでは、Elmを使用して簡単なショッピングカートのようなアプリケーションを開発します。
Elmには多くの利点があり、そのほとんどはクリーンなWebフロントエンドアーキテクチャを実現するのに非常に役立ちます。エルムは提供しています より優れたHTMLレンダリングパフォーマンスの利点 他の一般的なフレームワーク(React.jsでさえ)よりも。さらに、Elmを使用すると、開発者はコードを記述できます。コードは、実際には、JavaScriptなどの動的に型指定された言語を悩ますほとんどのランタイム例外を生成しません。
コンパイラーはタイプを自動的に推測し、わかりやすいエラーを発行して、開発者に認識させます 潜在的な問題 実行前。
NoRedInkには36,000行のElmがあり、1年以上生産された後も、ランタイム例外は1つも発生していません。 [ ソース ]
Elmを試すためだけに、既存のJavaScriptアプリケーション全体を変換する必要はありません。 JavaScriptとの優れた相互運用性により、既存のアプリケーションのごく一部を取得して、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のコメント 。
ここでは、カートをアイテムのリストとして定義しています。ここで、すべてのアイテムは2つの値(対応する製品と数量)を持つレコードです。各製品は、名前と価格が記載されたレコードです。
カートに商品を追加するには、その商品がカートにすでに存在するかどうかを確認する必要があります。
もしそうなら、私たちは何もしません。それ以外の場合は、商品を新しいアイテムとしてカートに追加します。リストをフィルタリングし、各アイテムを商品と照合し、結果のフィルタリングされたリストが空であるかどうかを確認することにより、商品がカートにすでに存在するかどうかを確認します。
小計を計算するために、カート内のアイテムを繰り返し処理し、対応する製品の数量と価格を見つけて、すべてを合計します。
これはカートとその関連機能が得ることができるのと同じくらいミニマルです。このコードから始めて、段階的に改善し、完全なWebコンポーネント、または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アーキテクチャは、Webフロントエンドを構造化するための単純なパターンを通じて開発者をガイドし、ほとんどの開発者がすでに精通している概念を通じてそれを行います。
あなたがあなたのコードの一部を扱っていると思うなら 更新 コントローラーとして、古き良きModel-View-Controller(MVC)パラダイムに非常によく似たものがあります。
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プログラムは、main関数を実行することから始まります。
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 }
ここで、main関数は、いくつかのモデル、ビュー、および更新関数を使用してElmプログラムを初期化します。いくつかの種類の製品とその価格を定義しました。簡単にするために、製品の数に制限はないと仮定しています。
更新機能は、私たちのアプリケーションが活気づくところです。
メッセージを受け取り、メッセージの内容に基づいて状態を更新します。これを、2つのパラメーター(メッセージと現在のモデル)を受け取り、新しいモデルを返す関数として定義します。
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といくつかのレイアウト属性をビューコードに直接埋め込みました。ただし、を合理化するライブラリが存在します スタイリングのプロセス ElmコードからのHTML要素。
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はReactと同様の仮想DOMアプローチを使用しているため、UIの変更は必要な場合にのみ実行され、迅速なパフォーマンスが保証されます。
Elmは静的に型付けされていますが、コンパイラーは型だけでなく多くのことをチェックできます。
Msg
に変更を加えましょう入力して、コンパイラがそれにどのように反応するかを確認します。
type Msg = Add Product | ChangeQty Product String
別の種類のメッセージを定義しました。カート内の商品の数量を変更するものです。ただし、更新関数でこのメッセージを処理せずにプログラムを再実行しようとすると、次のエラーが発生します。
前のセクションでは、数量値のタイプとして文字列を使用したことに注意してください。これは、値が文字列型の要素から取得されるためです。
新しい関数を追加しましょう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
を使用していますこちらのNumeralパッケージの関数。これにより、通常の通貨のフォーマット方法で数値がフォーマットされます。
100.5 -> $100.50 15.36 -> $15.36 21.15 -> $21.15
パッケージをElmリポジトリに公開すると、コード内のコメントに基づいてドキュメントが自動的に生成されます。カートモジュールのドキュメントを確認すると、実際の動作を確認できます。 ここに 。これらはすべて、このファイルにあるコメントから生成されました。 Cart.elm 。
最も明白な問題は、コンパイラ自体によって検出および報告されます。ただし、論理エラーから安全なアプリケーションはありません。
Elmのすべてのデータは不変であり、すべてが更新関数に渡されるメッセージを介して行われるため、Elmプログラムのフロー全体を一連のモデル変更として表すことができます。デバッガーにとって、Elmはターン制ストラテジーゲームのようなものです。これにより、デバッガーは、時間の移動など、いくつかの本当に驚くべき偉業を実行できます。プログラムの存続期間中に発生したさまざまなモデル変更間をジャンプすることにより、プログラムのフローを前後に移動できます。
デバッガーについて詳しく知ることができます ここに 。
だから、私たちは素敵なおもちゃを作りましたが、エルムは何か深刻なことに使用できますか?絶対に。
カートのフロントエンドを非同期のバックエンドに接続しましょう。それを面白くするために、特別なものを実装します。すべてのカートとその中身をリアルタイムで検査したいとします。実際には、このアプローチを使用して、オンラインショップやマーケットプレイスに追加のマーケティング/販売機能を導入したり、ユーザーに提案を行ったり、必要な在庫リソースを見積もったりすることができます。
そのため、カートをクライアント側に保存し、サーバーに各カートについてリアルタイムで通知します。
物事を単純にするために、Pythonを使用してバックエンドを実装します。バックエンドの完全なコードを見つけることができます ここに 。
これは、WebSocketを使用し、カートの内容をメモリ内で追跡するシンプルなWebサーバーです。簡単にするために、他のすべてのカートを同じページに表示します。これは、別のページに、または別のElmプログラムとして簡単に実装できます。今のところ、すべてのユーザーが他のユーザーのカートの概要を表示できます。
バックエンドを配置したら、Elmアプリを更新して、カートの更新をサーバーと送受信する必要があります。 JSONを使用してペイロードをエンコードします。これについては、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)
ライブラリは、Elmオブジェクトを取得してJSONエンコードされた文字列に変換するいくつかの関数(string
、int
、float
、object
など)を提供します。
デコーダーの実装 すべての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
サブスクリプションのリストを指定します。
サブスクリプションは、特定のチャネルでの変更をリッスンし、それらのメッセージをupdate
に転送するようにElmに指示する方法です。関数。
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は、他のフレームワーク(最も人気のあるフレームワークでさえ)がしばしば苦労する何かを提供します。つまり、巨大な冗長コードに巻き込まれることなく、堅牢なフロントエンドアプリケーションを構築する手段を提供します。
エルムはまた、多くの JavaScriptがもたらす困難 スマートコンパイラと強力なデバッガを組み合わせることによって。
Elmは、フロントエンドの開発者が長い間待ち望んでいたものです。動作を確認したので、自分で試してみて、構築することでメリットを享受してください。 次のWebプロジェクト エルムで。
format
を使用していますこちらのNumeralパッケージの関数。これにより、通常の通貨のフォーマット方法で数値がフォーマットされます。
100.5 -> 0.50 15.36 -> .36 21.15 -> .15
パッケージをElmリポジトリに公開すると、コード内のコメントに基づいてドキュメントが自動的に生成されます。カートモジュールのドキュメントを確認すると、実際の動作を確認できます。 ここに 。これらはすべて、このファイルにあるコメントから生成されました。 Cart.elm 。
最も明白な問題は、コンパイラ自体によって検出および報告されます。ただし、論理エラーから安全なアプリケーションはありません。
Elmのすべてのデータは不変であり、すべてが更新関数に渡されるメッセージを介して行われるため、Elmプログラムのフロー全体を一連のモデル変更として表すことができます。デバッガーにとって、Elmはターン制ストラテジーゲームのようなものです。これにより、デバッガーは、時間の移動など、いくつかの本当に驚くべき偉業を実行できます。プログラムの存続期間中に発生したさまざまなモデル変更間をジャンプすることにより、プログラムのフローを前後に移動できます。
デバッガーについて詳しく知ることができます ここに 。
だから、私たちは素敵なおもちゃを作りましたが、エルムは何か深刻なことに使用できますか?絶対に。
カートのフロントエンドを非同期のバックエンドに接続しましょう。それを面白くするために、特別なものを実装します。すべてのカートとその中身をリアルタイムで検査したいとします。実際には、このアプローチを使用して、オンラインショップやマーケットプレイスに追加のマーケティング/販売機能を導入したり、ユーザーに提案を行ったり、必要な在庫リソースを見積もったりすることができます。
そのため、カートをクライアント側に保存し、サーバーに各カートについてリアルタイムで通知します。
物事を単純にするために、Pythonを使用してバックエンドを実装します。バックエンドの完全なコードを見つけることができます ここに 。
これは、WebSocketを使用し、カートの内容をメモリ内で追跡するシンプルなWebサーバーです。簡単にするために、他のすべてのカートを同じページに表示します。これは、別のページに、または別のElmプログラムとして簡単に実装できます。今のところ、すべてのユーザーが他のユーザーのカートの概要を表示できます。
バックエンドを配置したら、Elmアプリを更新して、カートの更新をサーバーと送受信する必要があります。 JSONを使用してペイロードをエンコードします。これについては、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)
ライブラリは、Elmオブジェクトを取得してJSONエンコードされた文字列に変換するいくつかの関数(string
、int
、float
、object
など)を提供します。
デコーダーの実装 すべての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
サブスクリプションのリストを指定します。
サブスクリプションは、特定のチャネルでの変更をリッスンし、それらのメッセージをupdate
に転送するようにElmに指示する方法です。関数。
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は、他のフレームワーク(最も人気のあるフレームワークでさえ)がしばしば苦労する何かを提供します。つまり、巨大な冗長コードに巻き込まれることなく、堅牢なフロントエンドアプリケーションを構築する手段を提供します。
エルムはまた、多くの JavaScriptがもたらす困難 スマートコンパイラと強力なデバッガを組み合わせることによって。
Elmは、フロントエンドの開発者が長い間待ち望んでいたものです。動作を確認したので、自分で試してみて、構築することでメリットを享受してください。 次のWebプロジェクト エルムで。