Как инкапсулировать логику в дочерних элементах, таких как компоненты? - PullRequest
0 голосов
/ 06 января 2019

Я пытаюсь понять, как создавать повторно используемые компоненты, используя архитектуру Elmish в F # Bolero от WebSharper (например, ввод проверенной формы для повторного использования). Из всех примеров, которые я видел, родительский уровень верхнего уровня должен обрабатывать все сообщения / обновления и логику, а дочерние - просто для представлений. Мне интересно, есть ли способ обойти это, будь то, когда ребенок обрабатывает свое собственное состояние + сообщения, и распространяет определенные сообщения родителю (что я попытался в коде ниже), или есть другой дизайн, чтобы справиться с этим.

В моем конкретном случае я пытаюсь создать компонент ввода формы для имени пользователя, который проверяет, что ни одно из полей не является пустым. Мне не нравится идея, что родительский дескриптор обновляет отдельные поля 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

1 Ответ

0 голосов
/ 09 января 2019

Спасибо @rmunn и @hvester за ссылки, они помогли мне лучше понять Elmish и смогли найти решение. В качестве справки для всех, кто может наткнуться на это, вот решение. InternalMessage не нуждается в приватности, он просто скрывает эти случаи от функции обновления основной программы, чтобы можно было легко увидеть, какие сообщения им нужно обработать. Однако, если он общедоступен, компилятор выдаст ошибку, если вы попытаетесь сопоставить его в случае InternalMessage, не развернув сначала сообщение в 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 }
...