如何将 Html.Html msg 转换为 Html.Html Msg?

How to convert Html.Html msg to Html.Html Msg?

我正尝试在 Elm 中编写一个 TabControl,如下所示

module TabControl exposing (..)

import Html 
import Html.Attributes as Attributes
import Html.Events
import Array

type Msg
    = OnSelectedTabChanged
    | NoOp

type alias Model msg =
    { tabs : List (Tab msg)
    , selectedIndex : Int    
    }

constructor : Model msg
constructor =
    { tabs = []
    , selectedIndex = 0
    }

type alias Tab msg =
    { title : String
    , content : Html.Html msg 
    }

withTab : Tab msg -> Model msg -> (Model msg)
withTab tab model = 
    { model | tabs = model.tabs ++ [tab] }

render : Model msg -> Html.Html msg
render model =
    let 
        header = renderTabHeaders model
        content = renderSelectedTabContent model
    in
    Html.div [] [ header, content ]

renderTabHeaders : Model msg -> Html.Html msg
renderTabHeaders model =
    Html.div []    
    [
        Html.ul []        
        (
            model.tabs 
                |> List.map (\(tab) -> renderTabHeader tab)           
        )
    ]

renderTabHeader : Tab msg -> Html.Html msg
renderTabHeader tab =
    Html.li [Html.Events.onClick OnSelectedTabChanged] [Html.text tab.title]                        

renderSelectedTabContent : Model msg -> Html.Html msg
renderSelectedTabContent model =
    let 
        array = 
            Array.fromList model.tabs

        item = 
            Array.get model.selectedIndex array
    in
        case item of 
            Just value ->
                value.content
            Nothing ->
                Html.text ""

渲染
module Main exposing (..)

import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import TabControl

main =
  Browser.sandbox { init = 0, update = update, view = view }

view model =
  div []  
  [  
    TabControl.constructor
    |> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
    |> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
    |> TabControl.render
  ]

但是,我似乎无法正确输入 return 类型。

如果我将 renderTabHeader : Tab msg -> Html.Html msg 重构为 renderTabHeader : Tab msg -> Html.Html Msg 那么

Something is off with the body of the `renderTabHeaders` definition:

This `div` call produces:

    Html.Html Msg

But the type annotation on `renderTabHeaders` says it should be:

    Html.Html msg

如果我不这样做

Something is off with the body of the `renderTabHeader` definition:

This `li` call produces:

    Html.Html Msg

But the type annotation on `renderTabHeader` says it should be:

    Html.Html msg

如果我让每一个函数 return Html.Html Msg then

Something is off with the 1st branch of this `case` expression:

67|                 value.content
                    ^^^^^^^^^^^^^
The value at .content is a:

    Html.Html msg

But the type annotation on `renderSelectedTabContent` says it should be:

    Html.Html Msg

如何在 div 中包含元素,其中一些元素 return Html.Html msg 一些元素 return Html.Html Msg?或者,我如何在 Html.Html msgHtml.Html Msg

之间转换

啊..所以我可以将 value.content 映射到 return 像这样的 Msg

renderSelectedTabContent : Model msg -> Html.Html Msg
renderSelectedTabContent model =
    let 
        array = 
            Array.fromList model.tabs

        item = 
            Array.get model.selectedIndex array
    in
        case item of 
            Just value ->
                Html.map (\_ -> NoOp) value.content
            Nothing ->
                Html.text ""

如果您想公开您的 Msg 类型,您需要模块的用户提供一个函数,将您的 Msg 转换为他们的 msg

因此,如果顶级 Msg 类型如下所示:

type Msg
    = NoOp
    | TabControlMsg TabControl.Msg
    | SomethingElse

然后 render 可以采用接受 TabControl.Msg 和 returns 和 Msg 的函数,在这种情况下,它将是 TabControlMsg 构造函数。

view model =
  div []  
  [  
    TabControl.constructor
    |> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
    |> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
    |> TabControl.render TabControlMsg
  ]

然后您将调整模块中的代码以使用该功能:

render : (Msg -> msg) -> Model msg -> Html.Html msg
render toMsg model =
    let 
        header = renderTabHeaders model
        content = renderSelectedTabContent toMsg model
    in
    Html.div [] [ header, content ]

renderSelectedTabContent : (Msg -> msg) -> Model msg -> Html.Html msg
renderSelectedTabContent toMsg model =
    let 
        array = 
            Array.fromList model.tabs

        item = 
            Array.get model.selectedIndex array
    in
        case item of 
            Just value ->
                Html.map toMsg value.content
            Nothing ->
                Html.text ""