模块

Elm 具有模块,可帮助你以悦目的方式拓展代码库。在最基本的层面上,模块允许你将代码分到多个文件中。

定义模块

当围绕中央类型定义模块时,Elm 模块的运作效果最佳。例如,List 模块完全围绕 List 类型。因此,假设我们要围绕 Post 类型为博客网站构建一个模块。我们可以创建类似于此的内容

module Post exposing (Post, estimatedReadTime, encode, decoder)

import Json.Decode as D
import Json.Encode as E


-- POST

type alias Post =
  { title : String
  , author : String
  , content : String
  }


-- READ TIME

estimatedReadTime : Post -> Float
estimatedReadTime post =
  toFloat (wordCount post) / 220

wordCount : Post -> Int
wordCount post =
  List.length (String.words post.content)


-- JSON

encode : Post -> E.Value
encode post =
  E.object
    [ ("title", E.string post.title)
    , ("author", E.string post.author)
    , ("content", E.string post.content)
    ]

decoder : D.Decoder Post
decoder =
  D.map3 Post
    (D.field "title" D.string)
    (D.field "author" D.string)
    (D.field "content" D.string)

此处的唯一新语法是顶部的 module Post exposing (..) 行。这意味着该模块被称为 Post,并且外部使用者只能获得某些值。根据书面内容,wordCount 函数只能在 Post 模块使用。如同隐藏函数,这是 Elm 中最重要的技术之一!

注意:如果忘记添加模块声明,Elm 将使用此声明

module Main exposing (..)

这样可以为仅在一个文件中工作的初学者简化操作。他们第一天不应该面对模块系统!

模块拓展

随着应用程序变得更加复杂,你最终会向模块添加内容。正如我在 文件的生命周期 中所解释的那样,Elm 模块通常在 400 到 1000 行范围内。但是,当你有多个模块时,你将如何决定在何处添加新代码?

当代码为下方情况时,我尝试运用下列启发式方法

  • 唯一性 — 如果逻辑仅出现在一个位置,我会将顶级帮助程序函数分解在尽可能接近使用的位置。或许使用类似于 -- POST PREVIEW 的注释标头来指示以下定义与预览帖子相关。
  • 相似——比如说我们要在主页和作者页面中显示 文章 预览。在主页中,我们希望强调有趣的内容,所以我们希望更长的摘录。但在作者页面中,我们希望强调内容的广度,所以我们希望专注于标题。这些情况是相似的,但不是相同的,因此我们回到独特试探法中。单独编写逻辑即可。
  • 相同——在某个时间点,我们会拥有一堆独特的代码。没关系!但也许我们会发现,一些定义包含完全相同的逻辑。为此逻辑提供一个辅助函数!如果所有用法都位于一个模块中,则无需进行其他操作。如果你真想的话,可以添加一个注释头,如 -- 阅读时间

这些试探法都与在单个文件中创建辅助函数有关。当一堆辅助函数都围绕一个特定的自定义类型时,你才需要创建一个新模块。例如,你从创建一个 页.作者 模块开始,并且不会在辅助函数開始堆积前创建一个 文章 模块。在那个时间点,创建一个新模块会降低代码的导航和理解难度。如果不会,请返回较清晰的版本。更多模块并不会更好!采用保持代码简单且清晰的方法。

总之,请假定相似代码默认情况下是独特的。(它通常最终出现在用户界面中!)如果你在不同定义中看到逻辑是相同的,那么使用适当的注释头制作一些辅助函数。当你有许多关于特定类型的辅助函数时,考虑创建一个新模块。如果新模块使代码更清晰,很棒!否则,请返回。更多文件固有地不会更简单或更清晰。

注意:最常见的因模块而导致的困难是,曾经相同的内容后来变得相似。非常常见,特别是在用户界面中!人们通常会尝试创建一个处理所有不同情况的弗兰肯斯坦函数。添加更多参数。添加更复杂的参数。更好的方法是接受你现在有两种独特情况并将代码复制到这两个位置。按照你确切的需要对其进行自定。然后,查看是否有任何结果逻辑是相同的。如果有,则将其移到辅助函数中。你的长函数应分成多个较小的函数,而不是变得更长且更复杂!

使用模块

在 Elm 中,你的所有代码都位于 src/ 目录下是惯例。这是 elm.json 的默认设置。因此,我们的 文章 模块需要位于名为 src/Post.elm 的文件中。在此基础上,我们可以导入模块并使用其公开值。有四种方法可以实现此目标

import Post
-- Post.Post, Post.estimatedReadTime, Post.encode, Post.decoder

import Post as P
-- P.Post, P.estimatedReadTime, P.encode, P.decoder

import Post exposing (Post, estimatedReadTime)
-- Post, estimatedReadTime
-- Post.Post, Post.estimatedReadTime, Post.encode, Post.decoder

import Post as P exposing (Post, estimatedReadTime)
-- Post, estimatedReadTime
-- P.Post, P.estimatedReadTime, P.encode, P.decoder

我建议很少使用 exposing。最理想的情况是对零个或一个导入内容使用。否则,在阅读时,你将会很难弄清楚事物出自哪里。“等等,filterPostBy 又来自哪里?它接受哪些参数?”当你添加更多 exposing 时,代码阅读难度会直线上升。我倾向于将其用于 import Html exposing (..),但不用于其他任何内容。对于其他一切,我建议使用标准的 import,如果你有一个特别长的模块名称,可以使用 as

个结果与"" 相匹配

    无结果与"" 相匹配