Elm 中组件之间的通信

communication between components in Elm

假设我正在尝试遵循 Elm 架构并将我的工作流程拆分为 Users 和 Invoices,同时使用 StartApp.

用户有发票,但他们必须登录才能访问。

该模型可能看起来像这样:

type Model
   = NotLoggedIn Credentials
   | LoggedIn RealName (Maybe Invoices)

type alias State =
   { login : Model
   , notification : ......
   , ......


type alias Invoices = { invoices: List Invoice, ...... }

用户模块有动作:

type Action
   = Login (Result Http.Error String)
   | Logout
   | Submit
   ...

和更新功能:

update : Action -> Model -> (Model, Effects Action, Notification)
update action user =
   case (action, user) of
      (Login res, _) ->
         case res of
            Ok name ->
               (LoggedIn name Nothing, Effects.none, Info "Welcome!")
   ...

我跳过身份验证的细节,一切都很好。有趣的部分是 Login 动作。元组被发送到 main:

中的 step 函数
step : Action -> State -> (State, Effects Action)
step action state =
   case action of
      UserAction a ->
         let (newstate, ef, n) = User.update a state.login
         in ({ state | login = newstate, notification = n }, Effects.map UserAction ef)
      InvoiceAction a -> ......

所以用户已经登录。接下来我们要在 Invoice 模块中调用一些 init 操作。

但这应该如何正确完成呢?如何发起Invoice的动作来保持封装?我可以 return 除了 Effects.none 以外的其他东西吗?

一种方法是:

  • 在父组件中创建邮箱
  • 将该邮箱的地址传递给User更新
  • 用户更新returns一个发送消息到这个地址的效果
  • 收到邮件后,此邮箱会触发流向 Invoice
  • 的操作

elm-tutorial 中的这一章显示了这种模式。

这种情况可能可以通过对您的应用数据进行不同建模来解决。

据我了解,您有需要作为用户的操作和不需要用户的操作。 InvoiceAction 在我看来应该属于 UserAction。

所以,你可以

type MainAction = UserAction UAction | NonUserAction NonUAction 

type UAction = AuthAction Credentials | InvoiceAction Invoice.Action

用户模型将封装登录详细信息和发票详细信息。然后,在成功登录后,您可以重定向到 InvoiceAction。

update action model =
  case action of 
    AuthAction credentials -> 
      let 
        (isLoggedIn, notifications) = Authentication.check credentials
        model' = { model | credentials = credentials, notifications = notifications}
      in 
        if isLoggedIn
        then update (Invoice.initialize model'.credentials) model'
        else (model', Effects.none)

    InvoiceAction act -> 
      let 
        (invoices, fx) = Invoice.update model.credentials act model.invoices
      in 
        ({model | invoices = invoices}, Effects.map InvoiceAction fx)

实际操作由 Invoice 模块通过函数 initialize 提供,签名如 initialize: Credentials -> Action。这样做是为了保持封装。用户模块不需要知道特定的发票操作,只需要知道一个与初始化相关的操作,它可以通过该函数获取它。

另外,请注意我简化了更新签名并将通知移到了模型中。这是个人偏好,因为我认为通知没什么特别的。它们就像模型中的任何其他数据一样。当然,如果通知是通过某些自定义 StartApp 路由到端口并通过某些 JS 机制显示的任务,将它们保留在 return 中可能是有意义的。