【问题标题】:elm-architecture separate signal handling?榆树架构单独的信号处理?
【发布时间】:2016-02-21 18:52:11
【问题描述】:

我在网上的任何地方都找不到一个例子来回答这个问题:父组件如何响应来自子模块的不同操作? 考虑一个带有提交按钮的简单聊天消息输入:

// 子组件:带有提交按钮的文本输入

type Action
    = InputChanged String
    | MessageSent String


view : Signal.Address Action -> Model -> Html
view addr model =
    div []
        [ input
            [ type' "text"
            , value model.content
            , on "input" targetValue (\val -> Signal.message addr (InputChanged val))
            ]
            []
        , button
            [ type' "submit"
            , onClick addr (MessageSent model.content)
            ]
            [ text "Send" ]
        ]

持有该输入框的父组件如何响应该输入框可能出现的两个动作?传统的“路过”是这样的:

// 父组件,持有帖子列表和子组件

-- update

type Action
    = MessageBoxAction MessageBox.Action


update : Action -> Model -> Model
update act model =
    case act of
        MessageBoxAction msg ->
            { model |
                currentMessage = MessageBox.update msg model.currentMessage
            }

-- view

view : Signal.Address Action -> Model -> Html
view addr model =
    div []
        [ MessageBox.view (Signal.forwardTo addr MessageBoxAction) model.currentMessage ]

我想要做的是捕获来自该子组件的消息,并在正常的“刚刚通过”之外对其进行响应。像这样的:

case act of
    MessageBoxSubmit msg ->
        let updatedMessage = MessageBox.update msg model.currentMessage
            newPost = Posts.update msg model.posts
        in  
            { model |
                posts = model.posts :: [ newPost ]
                , currentMessage = updatedMessage
            }

但我不知道该怎么做,特别是因为在将地址转发给孩子时,您没有机会提供多个地址...

MessageBox.view (Signal.forwardTo addr MessageBoxAction) model.currentMessage

【问题讨论】:

标签: elm


【解决方案1】:

有两条主要途径可以做到这一点。

  1. 您可以更改 MessageBox update 的签名以返回您提供给 MessageBox init 的父操作。

    init : (String -> parentAction) -> Model
    init onSend = 
      { onSend = onSend
      , content = "" 
      }
    
    update : Action -> Model -> (Model, Maybe parentAction)
    
    update action model =
      case action of 
        MessageSent msg -> 
           let 
             model' = ...
           in 
             (model', Just (model.onSend msg))
    
        InputChanged str -> 
           let 
             model' = ...
           in 
             (model', Nothing)
    

在你的父模块中:

init = 
  { posts = [] 
  , currentMessage = MessageBox.init HandleSent
  }

update : Action -> Model -> Model
update act model =
    case act of
        MessageBoxAction msg ->
          let 
            (currentMessage', send) = MessageBox.update msg model.currentMessage
            model' = {model | currentMessage = currentMessage'} 
          in
            case send of 
              Nothing -> model'
              Just act -> update act model' -- you recursively call the update function with the new action that you received from the MessageBox.update
        HandleSent str -> { model | posts = str::model.posts }           
  1. 您可以为 MessageBox 模块中的操作提供解码器。

在 MessageBox 模块中

sentMessage action = 
  case action of
    MessageSent msg -> Just msg
    _ -> Nothing

在父母中

update : Action -> Model -> Model
update act model =
    case act of
        MessageBoxAction msg ->
          let 
            currentMessage' = MessageBox.update msg model.currentMessage
            model' = {model | currentMessage = currentMessage'} 
          in 
            case MessageBox.sentMessage msg of
              Nothing -> model'
              Just str -> update (HandleSent str) model'

        HandleSent str -> { model | posts = str::model.posts } 

【讨论】:

  • 太好了,谢谢。这些中的任何一个都可以,但我想知道,从更大的角度来看,我可能只是划分了不应该的模块,或者模块划分在错误的位置。
  • 一位伟人说:有两种人: - 分裂者:想把东西分成更小的部分。 - 混杂者:想要将事物混为一谈的人。一支伟大球队的秘诀是两者兼备,并且不让任何一方走极端。所以......我想这取决于代码的其余部分。这可能是过早分裂的情况。 :)
  • 想我会在有人注意的时候问...这两种解决方案看起来都很“循环”。 “一些事件 -> 更新 -> 发送另一个事件 -> 更新”。在应用程序的生命周期中,这会成为后来的枪吗?我真的只是希望能够“拆分”一个动作:MessageBoxAction msg -> case of MessageBox.MessageSent -> ...但我无法让编译者同意我的观点。
  • 它们看起来确实是圆形的,但实际上并非如此。这些解决方案背后的想法是,当您在父级中递归调用 update 时,您将始终使用另一个 Action 调用它,因此您将进入另一个分支。至于您想要做什么,您可以从 MessageBox 公开 Action 标签并在 parent 中检查它们。而不是module MessageBox (Model, init, Action, update, view) where 使用module Counter (Model, init, Action(..), update, view) where 这个解决方案破坏了封装,这就是我避免它的原因。
  • 我明白了...我使用 Flux 和 Redux 的日子让我非常厌倦任何远程循环或“虚拟循环”的事情(例如嵌入的 'do this then do that',因为它让事情变得非常将来很难追踪。另一方面,感谢Action(..) 的语法帮助。虽然它确实打破了封装,但我认为父母知道孩子的能力是合理的。反之,打破封装是我更关心的是……孩子知道父母的第二个你是在写老鼠窝。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 2013-07-30
相关资源
最近更新 更多