资讯专栏INFORMATION COLUMN

Elm入门实践(一)——基础篇

junbaor / 475人阅读

摘要:由于内容较多,计划分四篇,大致内容分布如下基础篇介绍基础。接下来让我们补全这一部分在第行我们引入了模块中函数,可以理解为当事件发生时,它会输出一个消息。我们有了数据,具备行为的视图,按行为改变数据的逻辑,却没有将它们粘合成一个应用。

简介

Elm 是一门专注于Web前端的纯函数式语言。你可能没听说过它,但一定听说过Redux,而Redux的核心reducer就是受到了Elm的启发。

随着整个React社区往函数式方向发展,Elm作为前端函数式编程的先驱和风向标,毫无疑问是值得去学习和借鉴的。

如果你打算开始函数式编程,与其阅读零碎的文章试图弄明白那些晦涩的Monad/Functor们,动手写点熟悉的东西也许是更好的方式。接下我会以常见的Counter/CounterList为例,一步步地带你了解如何使用Elm构建应用。

由于内容较多,计划分四篇,大致内容分布如下:

基础篇:Elm介绍、基础。使用在线编辑器实现Counter

类型篇:Elm的类型系统

进阶篇:本地工程的搭建,在本地实现Counter List

完结篇:处理副作用,Elm与Redux对比

下载和准备

本文的内容都基于官网提供的在线编辑器,可以稍后再配置本地环境

你可以在官网下载安装包,作为前端开发者,从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.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函数

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79639.html

相关文章

  • Elm入门实践(二)——类型

    摘要:如果不声明类型呢如果注释掉类型注解重新编译,还是会报错,只是错误信息变了,这次是第行即使没有显式的类型注解,的类型推导系统也会发挥作用,此处通过类型推导认为函数的参数应该是字符串,但是传入了数字,因此报错。 记得Facebook曾经在一次社区活动上说过,随着他们越来越多地使用Javascript,很快就面临了曾经在PHP上遇到的问题:这东西到底是啥? 动态语言就像把双刃剑,你可以爱死它...

    ZHAO_ 评论0 收藏0
  • SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数式编程

    摘要:函数式编程,一看这个词,简直就是学院派的典范。所以这期周刊,我们就重点引入的函数式编程,浅入浅出,一窥函数式编程的思想,可能让你对编程语言的理解更加融会贯通一些。但从根本上来说,函数式编程就是关于如使用通用的可复用函数进行组合编程。 showImg(https://segmentfault.com/img/bVGQuc); 函数式编程(Functional Programming),一...

    csRyan 评论0 收藏0
  • 尝试在JavaScript中构建个"Maybe"检测器

    摘要:我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。它导致了数不清的错误漏洞和系统崩溃,可能在之后年中造成了十亿美元的损失。这个函数将使用一个表示我们希望进行转换的函数参数,并返回一个包含转换结果的新参数。 翻译原文出处:Building a Maybe in JavaScript 鄙人翻译略差且略有出入,别见笑。 很多时候我们会碰到:Uncaught TypeError...

    bingo 评论0 收藏0
  • 2017值得瞥的JavaScript相关技术趋势

    摘要:值得一瞥的相关技术趋势从属于笔者的前端入门与工程实践,推荐阅读我的前端之路工具化与工程化获得更多关于年前端总结。的不少开发者都是的粉丝,他们的以及都是基于构建的。 2017值得一瞥的JavaScript相关技术趋势从属于笔者的Web 前端入门与工程实践,推荐阅读2016-我的前端之路:工具化与工程化获得更多关于2016年前端总结。本文主要内容翻译自,笔者对于每个条目进行了些许完善。本文...

    davidac 评论0 收藏0
  • 专治前端焦虑的学习方案

    摘要:不过今天我希望能够更进一步,不仅仅再抱怨现状,而是从我个人的角度来给出一个逐步深入学习生态圈的方案。最后,我还是想提到下对于的好的学习方法就是回顾参照各种各样的代码库,学习人家的用法与实践。 本文翻译自A-Study-Plan-To-Cure-JavaScript-Fatigue。笔者看到里面的几张配图着实漂亮,顺手翻译了一波。本文从属于笔者的Web Frontend Introduc...

    codeGoogle 评论0 收藏0

发表评论

0条评论

junbaor

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<