Elm入門實踐(一)——基礎篇

簡介

Elm 是一門專一於Web前端的純函數式語言。你可能沒據說過它,但必定據說過Redux,而Redux的核心reducer就是受到了Elm的啓發。javascript

隨着整個React社區往函數式方向發展,Elm做爲前端函數式編程的先驅和風向標,毫無疑問是值得去學習和借鑑的。html

若是你打算開始函數式編程,與其閱讀零碎的文章試圖弄明白那些晦澀的Monad/Functor們,動手寫點熟悉的東西也許是更好的方式。接下我會以常見的Counter/CounterList爲例,一步步地帶你瞭解如何使用Elm構建應用。前端

因爲內容較多,計劃分四篇,大體內容分佈以下:java

  1. 基礎篇:Elm介紹、基礎。使用在線編輯器實現Counterreact

  2. 類型篇:Elm的類型系統git

  3. 進階篇:本地工程的搭建,在本地實現Counter List程序員

  4. 完結篇:處理反作用,Elm與Redux對比github

下載和準備

本文的內容都基於官網提供的在線編輯器,能夠稍後再配置本地環境express

你能夠在官網下載安裝包,做爲前端開發者,從NPM下載也是很好的選擇,我的推薦後者npm

在安裝成功後,打開命令行輸入elm,會看到版本和幫助信息。

有用的學習資料

官網提供了文檔 和大量的examples ,然而我的一直不太喜歡Elm的一點就是官方文檔,不管是組織的合理性仍是完整性都有所欠缺,即便是像Syntax這樣務求全面的地方,也有不少遺漏的知識點,在無形中增長了初學者的學習成本。

本文接下來會盡可能講解涉及到的知識點,若是遇到困難,除了官網外,如下兩個連接也是不錯的補充:

  • Learn X in Y minutes:能夠當作是對官網Syntax 的補充,不只覆蓋了一些官網忽略的點,不少解釋也更加詳細

  • Elm for JS:針對Javascript開發者的常見疑點解答,學習過程當中有理解不了的地方不妨看看。

Hello world

按照套路,如今是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'很是類似,但有一點須要注意,它同時導入了Htmltext,而非只有text,讓咱們驗證一下,修改在線Hello world中的代碼:

import Html exposing (text)

main =
  Html.div [] [text "Hello, World!"]

咱們能夠在代碼中使用Html.div,證實Html一樣被導入了當前做用域。Html.div也是個函數,接收兩個數組,前者爲屬性數組,後者則是子元素。這種建立元素的方式其實很是常見:React.createElmenthyperscript都是這個套路。

沒用過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函數

Counter

有了Hello world的經驗,讓咱們再往前一步,建立一個在線版的Counter,這裏是React作的效果展現:https://jsfiddle.net/Kpaxqin/pu53jd89/2/

靜態View和數據

上面咱們使用了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

在這裏咱們建立了一個函數,第一行是 函數名 + 參數,和調用同樣都使用空格分隔,等號後面的就是函數體,除非一個函數特別簡單,多數時候咱們傾向於將函數體換行寫。

Update

有了靜態界面,接下來應該讓它「動」起來,響應用戶操做了。

首先,讓咱們定義兩種操做:

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

以前咱們建立了一個靜態的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的代碼。

相關文章
相關標籤/搜索