在Elm Architecture中编写程序

Agn*_*yay 4 composition elm elm-architecture

假设我想创建一个包含两个组件的网页,比如a Navbar和a Body.这两个组件不相互作用,可以独立开发.所以,我有两个榆树文件,每个文件都有以下组件:

type Model = ...

type Msg = ...

init : (Model, Cmd Msg)

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

view : Model -> Html Msg
Run Code Online (Sandbox Code Playgroud)

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

我试着写这样的东西:

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 = ...
Run Code Online (Sandbox Code Playgroud)

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

榆树建筑的推荐方法是什么来解决这个问题?这种模式在榆树建筑中是不是惯用的吗?

Dwa*_*oks 5

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

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

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

Body.view model.bodyhas typeHtml 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 ]
Run Code Online (Sandbox Code Playgroud)

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

类似的推理也适用于本案NavbarMsg

另外,上面的视图函数看起来也不是特别惯用。

查看代码有什么困扰您的?

注意: 此答案假设您使用的是 Elm 0.18


Gab*_*bor 5

我认为@dwaynecrooks涵盖了问题的技术方面.但我相信你的问题也意味着一个设计方面.


如何种植榆树代码?

正如其他人所指出的那样:从组件的角度思考几乎肯定会让你失去与榆树不那么吸引人的道路.(很多人都是从那里开始的.我和我的团队在2年前就开始了,我们花了3个应用程序/主要重新设计来达到我认为我们至少可以对基本面感到满意的程度.)

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

实际上,每个节点都在自己的Elm模块中实现:父母导入他们的孩子.您可能还认为您不必坚持通常的model/update/view签名,而应该关注应用程序域的特殊性.这就是我的读物 - 理查德费尔德曼在他的真实世界SPA示例应用程序中所做的事情.而Evan的文件谈话生活也与这个问题有关.


navbar+ 的情况body

关于你的具体案例 - 这不是一个罕见的案例 - 这是我的经验.如果我们说我们的webapp有一个导航栏,然后是一些正文,这是一个非常静态的应用程序描述.这种描述可能适合基于组件的思考,但如果您希望最终得到一个优雅的Elm应用程序则不太有用.

相反,它是值得尝试描述的行为在这个级别的抽象的,这听起来像这样你的应用程序:用户可以选择x,y,z在导航栏的项目.点击这些项目将影响项目q的方式,也影响身体任一ab方式.他还可以v在导航栏中单击,这将显示弹出窗口或执行操作w,这会将他从应用程序中导出.

如果你采用这种描述并应用我上面描述的逻辑,你应该最终得到某种设计,其中你的大多数导航栏都是在你的最高抽象层次中描述的.这包括项目x,y,z,v和行为a,b,w.现在,行为a可能意味着必须显示特定的富页面,其具有在较低抽象级别上描述的其自己的详细行为,而行为b可能意味着基于选择必须加载某些内容,并且再次详细说明这个加载过程是在较低的抽象层次上得出的.等等.

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

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