构建网络应用程序
如同我在前一页中所说,所有的模块都应该围绕一个中心类型构建。因此,如果我准备制作一个博客文章的网络应用程序,我会从这样的模块开始
Main
Page.Home
Page.Search
Page.Author
我会为每个页面添加一个模块,围绕Model
类型为中心。这些模块遵从具有典型Model
、init
、update
、view
的 Elm 架构,以及你需要的任何辅助函数。从这里开始,我会继续让这些模块不断增长。不断添加你需要的类型和函数。如果我注意到自己创建了一个带有几个辅助函数的自定义类型,我可能会将它移到其自己的模块中。
在我们看一些示例之前,我想强调一个重要的策略。
不要提前计划
请注意,我的Page
模块不会对未来做出任何猜测。我不尝试定义可以在多个地方使用的模块。我不会试图共享任何函数。这是有意的!
在我项目的早期,我总是对所有内容将如何衔接在一起有这些宏伟的想法。“编辑和查看帖子的页面都关心帖子,因此我将有一个Post
模块!”但是当我编写应用程序时,我发现只有查看页面应该有发布日期。而且我实际上需要不同方式地跟踪编辑内容,以便在关闭标签时缓存数据。而且,结果是它们实际上需要在服务器上以稍有不同的方式存储。等等。我最终将Post
变成一个巨大的混乱,以处理所有这些相互竞争的关注点,而且最终对两个页面都更糟。
如果仅从页面开始,则在事情“类似”但“不同”时能很轻松地发现这一点。用户界面的常规!因此在编辑和查看帖子时,我们很可能会得到一个结构、助手函数和 JSON 解码器有所不同的 EditablePost
类型和 ViewablePost
类型。也许这些类型复杂到理应拥有自己的模块。可能没有那么复杂!我只会编写代码,然后看看会发生什么。
这样做管用,因为编译器使进行大规模重构变得真的容易。如果我意识到自己的某个重大错误跨越了 20 个文件,我就会修复它们。
示例
您可以在以下开源项目中看到这种结构的示例
Culture Shock
习惯于 JavaScript 的人倾向于具备 JavaScript 特有的习惯、期待和焦虑。它们在那种情况下真的十分重要,但在过渡到 Elm 时它们可能造成一些相当严重的问题。
防御本能
在 文件的使用寿命 中,我指出了导致您在 Elm 中迷失方向的一些 JavaScript 的民间知识
“倾向于较短的文件。”在 JavaScript 中,您的文件越长,您就越有可能出现一些导致严重错误的暗中改变。但在 Elm 中,这是不可能的!您的文件可能会有 2000 行,但这种情况仍然不可能发生。“从一开始就完善架构。”在 JavaScript 中,重构极具风险性。在很多情况下,从头重写会更省力。但在 Elm 中,重构既经济又可靠!您能够自信地在 20 个不同的文件中进行更改。这些防御本能旨在保护您免受 Elm 中不存在的问题的侵害。在您脑中知道它和在您内心深处知道它是有区别的,并且我观察到当 JS 人员看到文件超过 400、600 或 800 行标记时,他们经常感到深深的不适。因此,我鼓励您继续突破您的行数限制!看看您的极限在哪里。尝试使用注释头,尝试制作助手函数,但将它们全部保留在一个文件中。拥有此亲身体验极具价值!
MVC
某些人看到了 Elm 架构并凭直觉将他们的代码分成用于
Model
、Update
和View
的不同模块。不要这样做!它导致边界不明确和有争议。当
Post.estimatedReadTime
用于update
和view
函数时,会发生什么?完全合理,但它并不明确属于某一个或多个。也许您需要一个Utils
模块?也许它实际上就是一种控制器类型的存在?产生的代码往往难以浏览,因为放置每个函数现在都是一个本体论问题,并且所有同事都有不同的理论。什么是真正的estimatedReadTime
?它的本质是什么?估计?理查德认为它的本质是什么?时间?如果您围绕一个类型构建每个模块,则很少会遇到此类问题。您可以使用一个
Page.Home
模块,其中包含您的Model
、update
和view
。您可以编写辅助函数。您最终添加一个Post
类型。您添加了一个estimatedReadTime
函数。也许有一天会有许多关于该Post
类型的方法,或许值得将其拆分为其自己的模块。使用此约定,您最终花费的时间要少得多,而不需要考虑和重新考虑模块边界。我发现代码也更清晰了。组件
来自 React 的人期望所有内容都是组件。积极尝试使组件成为 Elm 中灾难的秘诀。根本问题在于组件是对象
- 组件 = 本地状态 + 方法
- 本地状态 + 方法 = 对象
开始使用 Elm 并想知道“我该如何使用对象构建我的应用程序?”这将很奇怪。Elm 中没有对象!社区中的成员会建议您转而使用自定义类型和函数。
从组件角度出发思考会鼓励您根据应用程序的可视化设计创建模块。“有一个侧边栏,所以我需要一个
Sidebar
模块”。只需制作一个viewSidebar
函数并向它传递所需的任何参数即可。它甚至可能没有任何状态。也许是一两个字段?只需将其放入您已经拥有的Model
中。如果真的值得将其拆分为其自己的模块,那么您就会知道,因为您将拥有一个自定义类型,其中包含大量相关的辅助函数!重点是,编写
viewSidebar
函数不意味着您需要创建一个相应的update
和Model
。抵制这种本能。只需编写所需的辅助函数即可。