如何将 Html.Html msg 转换为 Html.Html 消息?

问题描述

我正在尝试在 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
  ]

但是,我似乎无法正确获取返回类型。

如果我将 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

如果我让每个函数都返回 Html.Html Msg 那么

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 中包含元素,其中一些返回 Html.Html msg,一些返回 Html.Html Msg?或者,我如何在 Html.Html msgHtml.Html Msg

之间转换

解决方法

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

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

type Msg
    = NoOp
    | TabControlMsg TabControl.Msg
    | SomethingElse

然后 render 可以接受一个接受 TabControl.Msg 并返回一个 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 ""
,

啊..所以我可以映射 value.content 以返回这样的消息

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 ""