首页 > 解决方案 > 如何像组件框架一样将逻辑封装在子级中?

问题描述

我试图了解如何使用 WebSharper 的 F# Bolero 中的 Elmish 架构创建可重用组件(例如,可重用的验证表单输入)。从我看到的所有示例中,顶级父级必须处理所有消息/更新和逻辑,而子级仅用于视图。我想知道是否有办法解决这个问题,是否让孩子处理自己的状态+消息,并将某些消息传播给父母(我在下面的代码中尝试过),或者是否有另一种设计来处理这个问题。

在我的具体情况下,我正在尝试为验证两个字段都不为空的用户名创建表单输入组件。我不喜欢让父处理更新各个字段 FirstName 和 LastName 的想法,它应该只关心获取 Submit 消息。如果您多次使用孩子,处理孩子产生的每条消息都会导致大量样板文件

注意:我提供的代码无法编译,因为我正在努力理解如何实现我的预期设计

open Elmish
open Bolero
open Bolero.Html

module NameInput =
    type Model = { FirstName : string; LastName : string }

    type Message =
        | ChangeFirstName of string
        | ChangeLastName of string
        | Submit of Model

    let update model msg =
        match msg with
        | ChangeFirstName s ->
            { model with FirstName = s}, Cmd.none
        | ChangeLastName s ->
            { model with LastName = s}, Cmd.none
        | Submit m ->
            m, Cmd.ofMsg (Submit m)

    type Component() =
        inherit ElmishComponent<Message, Model>()

        let invalidField s = s <> ""

        override this.View model dispatch =
            let fnClass = if (invalidField model.FirstName) then "invalid" else "valid"
            let lnClass = if (invalidField model.LastName) then "invalid" else "valid"
            div [] [
                label [] [ text "First Name: " ]
                input [ 
                    attr.``class`` fnClass 
                    on.change (fun e -> update model (ChangeFirstName (unbox e.Value))) 
                ]

                label [] [ text "Last Name: " ]
                input [ 
                    attr.``class`` lnClass 
                    on.change (fun e -> update model (ChangeLastName (unbox e.Value))) 
                ]

                button [ on.click (fun _ -> update model (Submit model)) ] [ text "Submit" ]
            ]

type Message =
    | NameSubmitted of NameInput.Message.Submit

type Model = { UserName : NameInput.Model }

let initModel = { UserName = { FirstName = ""; LastName = "" } }

let update msg model =
    match msg with
    | NameSubmitted name ->
        // Greet the user
        { model with UserName = name }, Cmd.none

let view model dispatch =
    concat [
        ecomp<NameInput.Component,_,_> 
            model.Username dispatch
    ]

type MyApp() =
    inherit ProgramComponent<Model, Message>()

    override this.Program =
        Program.mkProgram (fun _ -> initModel, Cmd.none) update view

标签: f#websharperfable-f#elmish

解决方案


感谢@rmunn 和@hvester 的参考,它帮助我更好地了解 Elmish 并能够提出解决方案。作为可能偶然发现此问题的任何其他人的参考,这里是解决方案。InternalMessage 不需要私有,它只是将这些情况从主程序的更新功能中隐藏起来,这样人们就可以很容易地看到他们需要处理哪些消息。如果它是公开的,如果您尝试匹配 InternalMessage 案例而不首先将 Message 解包到 InternalMessage 中,编译器将给出错误(因此程序员仍然很容易知道哪些消息是内部的)

module NameInput =
    type Model = { FirstName : string; LastName : string }

    type private InternalMessage =
        | ChangeFirstName of string
        | ChangeLastName of string

    type Message =
        | Internal of InternalMessage
        | Submit of Model

    let update msg model =
        match msg with
        | ChangeFirstName s ->
            { model with FirstName = s }
        | ChangeLastName s ->
            { model with LastName = s }

    type Component() =
        inherit ElmishComponent<Model, Message>()

        let invalidField s = s <> ""

        override this.View model dispatch =
            let fnClass = if (invalidField model.FirstName) then "invalid" else "valid"
            let lnClass = if (invalidField model.LastName) then "invalid" else "valid"
            div [] [
                label [] [ text "First Name: " ]
                input [ 
                    attr.``class`` fnClass 
                    on.change (fun e -> dispatch << Internal << ChangeFirstName <| unbox e.Value) 
                ]

                label [] [ text "Last Name: " ]
                input [ 
                    attr.``class`` lnClass 
                    on.change (fun e -> dispatch << Internal << ChangeLastName <| unbox e.Value) 
                ]

                button [ on.click (fun _ -> dispatch <| Submit model) ] [ text "Submit" ]
            ]

type Model = { Name : NameInput.Model }

let initModel = { Name = { FirstName = ""; LastName = "" } }

type Message =
    | NameInput of NameInput.Message

let update message model =
    match message with
    | NameInput ni ->
        match ni with
        | NameInput.Internal i ->
            { model with Name = model.Name |> NameInput.update i}
        | NameInput.Submit n ->
            { model with Name = n }

推荐阅读