Elm 是一門專一於Web前端的純函數式語言。你可能沒據說過它,但必定據說過Redux,而Redux的核心reducer就是受到了Elm的啓發。javascript
隨着整個React社區往函數式方向發展,Elm做爲前端函數式編程的先驅和風向標,毫無疑問是值得去學習和借鑑的。html
若是你打算開始函數式編程,與其閱讀零碎的文章試圖弄明白那些晦澀的Monad/Functor們,動手寫點熟悉的東西也許是更好的方式。接下我會以常見的Counter/CounterList爲例,一步步地帶你瞭解如何使用Elm構建應用。前端
因爲內容較多,計劃分四篇,大體內容分佈以下:java
基礎篇:Elm介紹、基礎。使用在線編輯器實現Counterreact
類型篇:Elm的類型系統git
進階篇:本地工程的搭建,在本地實現Counter List程序員
完結篇:處理反作用,Elm與Redux對比github
本文的內容都基於官網提供的在線編輯器,能夠稍後再配置本地環境express
你能夠在官網下載安裝包,做爲前端開發者,從NPM下載也是很好的選擇,我的推薦後者npm
在安裝成功後,打開命令行輸入elm,會看到版本和幫助信息。
官網提供了文檔 和大量的examples ,然而我的一直不太喜歡Elm的一點就是官方文檔,不管是組織的合理性仍是完整性都有所欠缺,即便是像Syntax這樣務求全面的地方,也有不少遺漏的知識點,在無形中增長了初學者的學習成本。
本文接下來會盡可能講解涉及到的知識點,若是遇到困難,除了官網外,如下兩個連接也是不錯的補充:
Learn X in Y minutes:能夠當作是對官網Syntax 的補充,不只覆蓋了一些官網忽略的點,不少解釋也更加詳細
Elm for JS:針對Javascript開發者的常見疑點解答,學習過程當中有理解不了的地方不妨看看。
按照套路,如今是Hello world時間,官網有在線版,代碼以下:
import Html exposing (text) main = text "Hello, World!"
很是簡單,卻隱含了幾個重要的知識點:
text "Hello, World"
是Elm中的函數調用,相似於JS中的text("Hello world")
,它將一個字符串轉換成Html文本。
在不少語言中,函數調用都是括號,參數用逗號分隔,好比fn(arg1, arg2)
,Elm的函數調用符爲空格,參數也使用空格分隔,這點初看起來彆扭,實際上並不難適應。
調用符和分隔參數都是空格,如何區分呢?
答案是不須要區分,Elm全部函數都是自動柯里化的,對於柯里函數
fn(arg1, arg2)
和fn(arg1)(arg2)
等價,使用空格做爲調用符,即(fn arg1) arg2
,注意這裏的括號僅用來表示代碼執行順序,省略後即爲fn arg1 arg2
第一行代碼的import Html exposing (text)
是模塊引用,和ES6中的import {text} from 'Html'
很是類似,但有一點須要注意,它同時導入了Html
和text
,而非只有text
,讓咱們驗證一下,修改在線Hello world中的代碼:
import Html exposing (text) main = Html.div [] [text "Hello, World!"]
咱們能夠在代碼中使用Html.div
,證實Html
一樣被導入了當前做用域。Html.div
也是個函數,接收兩個數組,前者爲屬性數組,後者則是子元素。這種建立元素的方式其實很是常見:React.createElment和hyperscript都是這個套路。
沒用過
React.createElement
?JSX幫你作了而已
因爲Html包含了幾乎全部瀏覽器標籤的渲染函數,一個個寫進exposing難免繁瑣(想象下有多少原生標籤)。讓咱們再作一點微小的工做,使用exposing(..)
來讓代碼更加簡潔。同時,咱們嘗試給div添加class屬性
import Html exposing (..) import Html.Attributes exposing (..) main = div [class "hello"] [ span [] [text "Hello, World!"] ]
因爲不夠嚴謹,並不推薦在生產代碼中使用
exposing(..)
和渲染標籤同樣,在Elm中屬性的建立也是由函數完成的,上例咱們使用了Html.Attributes
模塊的class
函數
有了Hello world的經驗,讓咱們再往前一步,建立一個在線版的Counter,這裏是React作的效果展現:https://jsfiddle.net/Kpaxqin/pu53jd89/2/
上面咱們使用了Html.div
來渲染div,同理,咱們可使用Html.button
來渲染按鈕。稍微修改下剛纔的代碼便可:
import Html exposing (..) main = div [] [button [] [text "-"] ,text (toString 1) ,button [] [text "+"] ]
如今div有三個子元素——兩個button和一個數字,一個靜態的Counter就這麼構建出來了,很是簡單。
抽象是程序員的基本素養,把數字1
寫死在視圖裏顯然是很業餘的表現。將渲染視圖這個行爲封裝成函數更加合理:
import Html exposing (..) view model = div [] [ button [] [text "-"] ,text (toString model) ,button [] [text "+"] ] initModel = 3 main = view initModel
在這裏咱們建立了一個函數,第一行是 函數名 + 參數
,和調用同樣都使用空格分隔,等號後面的就是函數體,除非一個函數特別簡單,多數時候咱們傾向於將函數體換行寫。
有了靜態界面,接下來應該讓它「動」起來,響應用戶操做了。
首先,讓咱們定義兩種操做:
type Msg = Increment | Decrement
接下來,定義這兩種操做如何改變數據:
update msg model = case msg of Increment -> model + 1 Decrement -> model - 1
update函數中的msg是咱們剛剛定義的Msg類型的消息,model則是當前數據的值,若是你瞭解Redux的話必定會想:這不就是Reducer的(action, state)=> nextState
嗎?確實如此,Reducer的概念正是受到了Elm的啓發,在最終章咱們會繼續探討這個話題
還有一點你可能已經注意到了,不管是前面的view仍是這裏的update函數,它們都沒有return關鍵字!這是函數式語言很是重要的特色:一切都是expression,都須要有返回值。這強制你去表達要什麼
,而不是作什麼
。
簡單的例子就是case語句和if語句:
/* case statement */ //elm case arg of value1 -> result1 value2 -> result2 //javascript switch (expression) { case value1: /*do sth*/ return result1; break case value2: /*do sth*/ return result2; break /* if statement */ //elm //else is required if 3 > 2 then "cat" else "dog" //javascript if (3 > 2) { return 'cat' } else { //else statement is optional return 'dog' }
對要什麼
的分解在函數式思惟中很是重要,一般會和遞歸聯繫起來,本文並不打算深刻,建議有興趣瞭解的朋友能夠學習Elm官網Examples中 functional stuff - recursion 小節下的例子
以前咱們建立了一個靜態的View,它沒有任何事件相關的代碼,所以也不可能響應用戶行爲。接下來讓咱們補全這一部分
import Html exposing (..) import Html.Events exposing (onClick) view model = div [] [ button [onClick Decrement] [text "-"] ,text (toString model) ,button [onClick Increment] [text "+"] ]
在第2行咱們引入了Html.Events模塊中onClick
函數,onClick Decrement
能夠理解爲當click事件發生時,它會輸出一個Decrement
消息。
但是向誰輸出?輸出的消息如何傳遞給update函數呢?讓咱們回顧一下全部的代碼:
import Html exposing (..) import Html.Events exposing (onClick) type Msg = Increment | Decrement update msg model = case msg of Increment -> model + 1 Decrement -> model - 1 view model = div [] [ button [onClick Decrement] [text "-"] ,text (toString model) ,button [onClick Increment] [text "+"] ] initModel = 3 main = view initModel
目前爲止,界面仍然是靜態的。咱們有了數據,具有行爲的視圖,按行爲改變數據的邏輯,卻沒有將它們粘合
成一個應用。
Elm爲咱們提供了這樣的方法,在Html.App模塊中
import Html.App as App main = App.beginnerProgram {model = initModel, view = view, update = update}
注意這裏的方法名叫beginnerProgram
,它的參數分別表明了:Model
, View
, Update
,這是,Elm架構的最簡形態(不考慮異步等反作用),也是任何符合Elm架構的組件都必不可少的三個部分,完整代碼以下:
import Html exposing (..) import Html.Events exposing (onClick) import Html.App as App type Msg = Increment | Decrement update msg model = case msg of Increment -> model + 1 Decrement -> model - 1 view model = div [] [ button [onClick Decrement] [text "-"] , text (toString model) , button [onClick Increment] [text "+"] ] initModel = 3 main = App.beginnerProgram {model = initModel, view = view, update = update}
經過這個簡單的Counter相信你已經對Elm有了初步的瞭解,若是回顧上面的代碼你會發現其實函數式語言並非那麼晦澀或高深。
下一章中咱們將會了解Elm的類型,並用類型優化Counter的代碼。