首页 > 解决方案 > 如何与 Elm 中的多态子组件进行通信?

问题描述

我的主程序有一个update功能

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

为了与子组件通信,我们可以添加另一个变体并将我们的消息包装在一个新消息中

type alias Model =
    { ...
    , child : Child.Model
    }

type Msg
    = ...
    | ChildMsg Child.Msg

update msg model =
    case msg of
        ...

        ChildMsg childMsg ->
          let
              ( childModel, cmd ) =
                  Child.update childMsg model.child

              updatedModel =
                  { model | child = childModel }

              childCmd =
                Cmd.map ChildMsg cmd
          in
               ( updatedModel, childCmd )

但是,如果我的子组件update功能的类型与父组件不匹配,这似乎具有挑战性。考虑一个具有多态更新函数的孩子:

-- PolymorphicChild.elm

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

从这个模块运行命令时,我必须包装它

PolymorphicChild.someCommand : Cmd (Msg Foo)

PolymorphicChild.someCommand
  |> Cmd.map PolymorphicChild

但是,这会产生一个Msg (PolymorphicChild.Msg Foo),而不是Msg PolymorphicChild.Msg我的应用程序所期望的。

The right side of (|>) is causing a type mismatch.

(|>) is expecting the right side to be a:

    Cmd (PolyMorphicChild.Msg Foo) -> a

But the right side is:

    Cmd Polymorphic.Msg -> Cmd Msg

我尝试添加一个多态参数App.Msg

-- App.elm

type Msg a =
   = ..
   | PolymorphicChildMsg (PolymorphicChild.Msg a) 

但它基本上炸毁了我的整个程序。每个涉及的功能都App.Msg需要以某种方式更改以与新的子组件一起使用。

如何统一这两种类型并使两个组件一起工作?

标签: functional-programmingpolymorphismelmunificationunion-types

解决方案


我认为问题在于您在公开Msg类型中泄露了太多信息。您对 type 参数的使用Msg a似乎仅限于一组已知类型,即AuthorCategoryPostTag。从略读你的代码,它看起来永远不会是这四个中的一个,所以你以这种方式抽象事物的事实应该保留在这个模块中,而不是暴露它并加重任何其他可能拉动的代码这在。

我认为您需要将抽象降低一个级别以避免参数化您的公共Msg类型。我建议使用四个具体的构造函数Msg而不是对其进行参数化,并将抽象转换为辅助LoadInfo a类型:

type alias LoadInfo a =
    { worker : Worker a
    , url : Url
    , result : Result Http.Error ( Int, List a )
    }

type Msg
    = LoadPost (LoadInfo Post)
    | LoadCategory (LoadInfo Category)
    | LoadTag (LoadInfo Tag)
    | LoadAuthor (LoadInfo Author)

推荐阅读