Elm 0.17:如何订阅兄弟/嵌套组件更改

时间:2016-05-19 15:46:18

标签: architecture frontend elm

请参阅完整实施,并在此处提供已接受答案的建议:https://github.com/afcastano/elm-nested-component-communication

=============================================== ==================

我有一个有两个孩子的父组件。 See working example

使用Elm Architecture,如果左子项中的任何计数器发生变化,如何更新正确的孩子?

目前,我让父组件读取左子模型的嵌套属性并将其设置为正确的子模型,但在我看来,父母不应该知道关于内部结构的内容。孩子。

这些是模型和消息:

type alias MainModel =
  { counterPair : CounterPair
  , totals: Totals
  }

type alias CounterPair =
  {
    greenCounter : Counter
  , redCounter : Counter
  }

type alias Counter =
  {
    value : Int
  , totalClicks : Int  
  }

type alias Totals =
  {
    totalRed : Int
  , totalGreen : Int
  , combinedTotal : Int
  }

type MainMsg
  =  UpdateCounterPair CounterPairMsg
  |  UpdateTotals TotalsMsg


type alias RedVal = Int
type alias GreenVal = Int

type TotalsMsg
  = UpdateTotals RedVal GreenVal

如您所见,Main模型包含两个子模型。该对模型依次包含两个计数器模型。

Total模型对Pair组件的CounterModel的更改感兴趣。

为此,主更新功能如下:

updateMain: MainMsg -> MainModel -> MainModel
updateMain msg model =
  case msg of
    UpdateCounterPair counterPairMsg ->
    let 
      counterPairModel = updateCounterPair counterPairMsg model.counterPair
      totalsModel = updateTotals (UpdateTotals counterPair.redCounter.value counterPair.greenCounter.value) model.totals
    in
      {model | counterPair = counterPairModel, totals = totalsModel}

我不喜欢的事情就在这一行:

updateTotals (UpdateTotals counterPair.redCounter.value counterPair.greenCounter.value) model.totals

1 - 主模块需要知道如何获取计数器的值,以便它可以将更新传递给updateTotal函数。

2 - Main模块还需要了解Totals模块的union类型的内部结构,以便它可以使用UpdateTotals构造函数。

在没有父母了解模型结构细节的情况下,Totals组件是否可以订阅Pair组件?

非常感谢。

2 个答案:

答案 0 :(得分:8)

如果您有一个组件,并且您希望该组件具有副作用,换句话说,要在其自身之外产生效果,您可以将一些信息(数据)与模型和Cmd一起返回:

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

父母可以使用SomeInfo来决定在其他地方做什么。

如果需要从外部更新组件,最好通过自定义更新功能公开。在计数器的情况下,这将是这样的:

updateValue: Int -> Model -> Model

这样,您可以自由地在模块中使用您想要的任何表示。然后,计数器的父级可以在遇到某些条件时更新计数器的模型。

value : Model -> Int这样的函数也可用于从计数器模型中提取信息。

所有这些都确保您在向模块用户呈现用于检索和更新数据的界面的同时保持封装。

答案 1 :(得分:1)

您确定要在三个组件之间拆分功能吗?看起来非常耦合。根据你的描述,我想象一下这些内容:

type alias Model = {green:int, red:int}
type Msg = IncrementGreen
         | IncrementRed

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
  IncrementGreen -> {model|green=(model.green+1)}
  IncrementRed -> {model|red=(model.red+1)}

在该示例中,只有cone组件,它拥有两个计数器的状态,并处理消息以更新它。现在我们有一个单一的规范状态,但我们仍然希望有一个模块化的视图。

您可以使用函数从父级状态的一部分映射到子级的状态来实现派生传递给组件的状态。

view : Model -> Html Msg
view m =
  div [][
    (Html.map mapRedMsg <| Counter.view (ToCounterModel m.red)),
    (Html.map mapGreenMsg <| Counter.view (ToCounterModel m.green)),
    (Labels.view (ToLabelsState m))
  ]

这样做,你只有一个事实来源(你在模型中保留的状态),每次更新时你都会以更具声明性的方式将它推送给孩子,而不必手动复制它。 / p>