Elm 0.17:如何订阅 sibling/nested 组件更改

Elm 0.17: How to subscribe to sibling/nested component changes

在此处查看包含已接受答案建议的完整实施:https://github.com/afcastano/elm-nested-component-communication

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

我有一个 parent 组件和两个 children。 See working example

使用 Elm 架构,如何在左侧 child 中的任何计数器发生变化时更新右侧 child?

目前,我有 parent 组件读取左侧 child 模型的嵌套 属性 并将其设置为右侧 child 模型,但是它在我看来 parent 不应该对 children.

的内部结构了解那么多

这些是模型和消息:

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

如您所见,主模型包含两个子模型。对模型反过来,也包含两个计数器模型。

Total 模型对 Pair 组件的 CounterModels 的变化很感兴趣。

要做到这一点,主更新函数应该是这样的:

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 模块联合类型的内部结构,以便它可以使用 UpdateTotals 构造函数。

在 parent 不知道模型结构细节的情况下,是否有任何其他方法可以让 Totals 组件订阅 Pair 组件?

非常感谢。

您确定要将功能拆分到三个组件中吗?看起来很耦合。根据您的描述,我可以想象出以下内容:

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 组件,它拥有两个计数器的状态并处理消息以更新它。现在我们有一个单一的规范状态,但我们仍然希望有一个模块化的视图。

您可以使用函数从 parent 的部分状态映射到 children 的状态来导出传递给组件的状态。

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))
  ]

这样一来,您就有了单一的真实来源(您保存在模型中的状态),每次更新时,您都可以以更明确的方式将其推送到 children,而无需手动复制它。

如果您有一个组件并且您希望该组件产生副作用,换句话说,在其自身之外产生影响,您可以 return 一些信息(数据)与模型一起和命令:

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

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

如果需要从外部更新组件,最好通过自定义更新函数公开它。在计数器的情况下,它看起来像这样:

updateValue: Int -> Model -> Model

这样,您就可以在模块内自由使用任何您想要的表示形式。计数器的父级可以在遇到某些情况时更新计数器的模型。

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

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