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的代码。

相关文章
相关标签/搜索