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