4

假设我想创建一个包含两个组件的网页,比如 aNavbar和 a Body。这两个组件互不交互,可以独立开发。所以,我有两个 elm 文件,每个文件都包含以下组件:

type Model = ...

type Msg = ...

init : (Model, Cmd Msg)

update : Msg -> Model -> (Model, Cmd Msg)

view : Model -> Html Msg

假设它们都工作正常,我们如何组合它们来制作一个同时具有这两个组件的程序?

我试着写这样的东西:

type Model = {body : Body.Model , navbar : Navbar.Model}
type Msg = BodyMsg Body.Msg | NavbarMsg Navbar.Msg

view : Model -> Html Msg
view model = div [] [Body.view model.body, Navbar.view model.navbar]

update : Msg -> Model -> (Model, Cmd Msg)
update = ...

当我尝试编写这个更新函数时,上面的内容很快变得很难看。特别是,一旦我从Navbar.updateor中提取出 Cmd 的更新函数中的 Msg Body.update,我如何提取它们并将它们再次反馈给这些函数?此外,上面的视图函数看起来并不特别地道。

解决此问题的 elm-architecture 推荐方法是什么?这种模式在 elm-architecture 中是惯用的吗?

4

3 回答 3

5

我认为@dwaynecrooks 涵盖了问题的技术方面。但我相信你的问题也暗示了设计方面。


如何增长 Elm 代码?

正如其他人指出的那样:从组件的角度思考几乎肯定会让你走上一条不那么吸引人的 Elm 道路。(很多人都是从那里开始的。我和我的团队在 2 年前就开始了,我们花了 3 次应用程序/重大重新设计才达到我认为我们至少可以对基本原理感到满意的地步。)

我建议您应该将 Elm 应用程序视为一棵树,而不是组件。树的每个节点都代表一个抽象级别,并描述了您的应用程序在该级别上的行为。当您感觉给定级别的细节太多时,您可以开始考虑如何将新的、较低级别的抽象作为子节点引入。

在实践中,每个节点都在自己的 Elm 模块中实现:父母导入他们的孩子。您可能还认为您不必坚持通常的model/update/view签名,而是应该关注应用程序域的特殊性。这就是——在我的阅读中——Richard Feldman 在他的Real World SPA 示例应用程序中所做的事情。埃文的档案谈话生活也与这个问题有关。


navbar+的情况body

关于你的特殊情况——这并不罕见——这是我的经验。如果我们说我们的 web 应用程序有一个导航栏,然后是一些正文,那么这是对应用程序的一个相当静态的描述。这种描述可能适合基于组件的思维,但如果你想最终得到一个优雅的 Elm 应用程序,它就没有多大帮助。

相反,值得尝试在这个抽象级别描述您的应用程序的行为这可能听起来像这样:用户可以选择导航栏中x的项目。单击这些项目将影响项目的方式,也会影响身体的方式,或方式。他还可以单击导航栏中的 ,这将显示一个弹出窗口或 do ,这会将他从应用程序中注销。yzqabvw

如果您采用此描述并应用我上面描述的逻辑,您可能最终会得到某种设计,其中大部分导航栏都以最高抽象级别进行描述。这包括项目, x,y和行为, z, 。现在,行为可能意味着必须显示一个特定的、丰富的页面,该页面具有在较低抽象级别上描述的自己的详细行为,而行为可能意味着必须根据选择加载一些内容,并再次加载详细信息这个加载过程是在较低的抽象级别上制定的。等等。vabwab

当我们开始以这种方式处理问题时,找出如何拆分逻辑以及如何处理特殊情况变得更加直接。例如,我们意识到,当有人说一个页面想要“在导航栏中”显示一些东西时,她真正的意思是导航栏应该为特定页面折叠(或转换),以便页面可以显示它自己的标题在那个区域。

专注于应用程序的行为而不是静态内容区域对此有所帮助。

于 2018-07-05T20:20:35.037 回答
4

是的,你在正确的道路上。

在视图中您需要使用Html.map.

view : Model -> Html Msg
view model =
  div []
    [ Html.map BodyMsg (Body.view model.body)
    , Html.map NavbarMsg (Navbar.view model.navbar)
    ]

Body.view model.body有类型Html Body.Msg要求我们使用Html.map来获得正确的Html Msg. 同样对于Navbar.view model.navbar

而且,对于update你会这样写的函数:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    BodyMsg bodyMsg ->
      let
        (newBody, newCmd) = Body.update bodyMsg model.body
      in
        { model | body = newBody } ! [ Cmd.map BodyMsg newCmd ]

    NavbarMsg navbarMsg ->
      let
        (newNavbar, newCmd) = Navbar.update navbarMsg model.navbar
      in
        { model | navbar = newNavbar } ! [ Cmd.map NavbarMsg newCmd ]

在这种BodyMsg情况下,newBody有类型Body.Model,所以我们可以body在其中设置字段model。但是,newCmd有类型Cmd Body.Msg,所以在我们返回之前,我们需要使用Cmd.map来获取正确的返回类型Cmd Msg

类似的推理可以用于这种NavbarMsg情况。

此外,上面的视图函数看起来并不特别地道。

查看代码有什么困扰你的地方?

注意 这个答案假设您使用的是 Elm 0.18

于 2018-07-04T01:54:18.597 回答
3

这基本上是要走的路,是的。在 GitHub 上的 Elm 中有一个大型 SPA 的流行示例。您可以在这里看到负责映射每个页面的消息的 Main.elm:https ://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm

您的示例中缺少的一件事是绝对需要的消息类型的映射。我猜你为了写一个更小的帖子而忽略了这一点,但根据我的经验,这是样板文件的实际部分。

但是,您应该尽量不要模仿像 React 这样的组件方法。只需使用函数。SPA 中的单独页面是一个示例,其中有一个专门的消息类型和相应的功能是有意义的,就像使用程序一样。

本文解释了扩展大型 Elm 应用程序的一般方法,还提到了没有为每个组件提供专用消息的要点。

于 2018-07-03T15:18:55.260 回答