【问题标题】:How to create SPA with Elm 0.19?如何使用 Elm 0.19 创建 SPA?
【发布时间】:2018-09-27 20:36:02
【问题描述】:

我正在尝试使用 Elm 构建一个 SPA 并创建三个页面,它们应该显示内容,这取决于 URL。

这三个页面的内容相似,例如Page.elm

module Page.NotFound exposing (Msg(..), content)

import Html exposing (..)
import Html.Attributes exposing (..)



---- UPDATE ----


type Msg
    = NotFoundMsg


content : Html Msg
content =
    p [] [ text "Sorry can not find page." ]

Main.elm,我有以下代码:

module Main exposing (Model, Msg(..), init, main, update, view)

import API.Keycloak as Keycloak exposing (..)
import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Json.Decode as Decode
import Page.Account as Account
import Page.Home as Home
import Page.NotFound as NotFound
import Route
import Url
import Url.Parser exposing ((</>), Parser, int, map, oneOf, parse, s, string)



---- MODEL ----


type alias Model =
    { key : Nav.Key
    , url : Url.Url
    , auth : Result String Keycloak.Struct
    }


init : Decode.Value -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
    ( Model key url (Keycloak.validate flags), Cmd.none )



---- ROUTE ----


type Route
    = Account



---- UPDATE ----


type Msg
    = PageNotFound NotFound.Msg
    | PageAccount Account.Msg
    | PageHome Home.Msg
    | LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        LinkClicked urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )

                Browser.External href ->
                    ( model, Nav.load href )

        UrlChanged url ->
            ( { model | url = url }
            , Cmd.none
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none



---- VIEW ----


info : Html Msg
info =
    header [] [ text "Header" ]


createLink : String -> Html Msg
createLink path =
    a [ href ("/" ++ path) ] [ text path ]


navigation : Html Msg
navigation =
    ul []
        [ li [] [ createLink "home" ]
        , li [] [ createLink "account" ]
        ]


content : Model -> Html Msg
content model =
    main_ []
        [ case parse Route.parser model.url of
            Just path ->
                matchedRoute path

            Nothing ->
                NotFound.content
        ]


matchedRoute : Route.Route -> Html Msg
matchedRoute path =
    case path of
        Route.Home ->
            Home.content

        Route.Account ->
            Account.content


body : Model -> List (Html Msg)
body model =
    [ info
    , navigation
    , content model
    ]


view : Model -> Browser.Document Msg
view model =
    { title = "Cockpit"
    , body = body model
    }



---- PROGRAM ----


main : Program Decode.Value Model Msg
main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        , onUrlChange = UrlChanged
        , onUrlRequest = LinkClicked
        }

编译器抱怨:

-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm

The 2nd branch of this `case` does not match all the previous branches:

104|         [ case parse Route.parser model.url of
105|             Just path ->
106|                 matchedRoute path
107|
108|             Nothing ->
109|                 NotFound.content
                     ^^^^^^^^^^^^^^^^
This `content` value is a:

    Html NotFound.Msg

But all the previous branches result in:

    Html Msg

Hint: All branches in a `case` must produce the same type of values. This way,
no matter which branch we take, the result is always a consistent shape. Read
<https://elm-lang.org/0.19.0/union-types> to learn how to “mix” types.

-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm

Something is off with the 2nd branch of this `case` expression:

120|             Account.content
                 ^^^^^^^^^^^^^^^
This `content` value is a:

    Html Account.Msg

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

    Html Msg

-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm

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

117|             Home.content
                 ^^^^^^^^^^^^
This `content` value is a:

    Html Home.Msg

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

    Html Msg
Detected errors in 1 module.

我知道类型错了,但不知道如何证明。

我怎样才能让它工作?

我还查看了https://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm 中的示例,但不知道它是如何工作的。

【问题讨论】:

  • 您能分享一下 API.Keycloak 内部发生了什么吗?

标签: elm elm-architecture


【解决方案1】:

您有多个Msg 类型,这没关系,但可能会导致混淆。简而言之:Main.MsgNotFound.Msg 不是同一类型。

函数matchedRoute返回一个Html Main.Msg,而函数NotFound.content返回一个Html NotFound.Msg;完全不同的类型。

你已经完成了 99%,因为你有一个 PageNotFound NotFound.Msg 类型的构造函数,它产生了一个 Main.Msg。这允许您将NotFound.Msg包装Main.Msg 中。这应该是在您的Nothing -&gt; 分支中执行PageNotFound NotFound.content 的问题。

【讨论】:

  • 解释是对的,但我不认为这个建议是正确的。我认为你需要在Nothing -&gt; 分支中做的是NotFound.content |&gt; Html.map PageNotFound。这会将Html NotFound.Msg 映射到Html Main.Msg,方法是将NotFound.Msg 包装在PageNotFound 中,Main.Msg 类型。
  • 这是在 elm-spa-example 中所做的:github.com/rtfeldman/elm-spa-example/blob/master/src/…
【解决方案2】:

问题是NotFound.content所引用的Msg类型为NotFound.MsgMain.matchedRoute所引用的Msg类型为Main.Msg,这些并不会自动统一。因此,当您在 case 表达式的不同分支中使用它们时,编译器会告诉您它们是不同的,无法统一为单一类型以供 case 表达式返回。

因此,您必须将一个转换为另一个,通常的方法是向“外部”消息类型 (Main.Msg) 添加一个变体,该变体包含“内部”消息类型 (NotFound.Msg) .幸运的是,您已经将该变体添加为 PageNotFound NotFound.Msg,因此我们可以继续。

下一步是将NotFound.Msgs 包装在PageNotFounds 中。不幸的是,我们很少能单独处理NotFound.Msg 的值,它通常被包裹在一些其他类型中,比如HtmlCmd,这更难处理。幸运的是,Evan 有足够的预知来预测这种情况,并添加了Cmd.mapHtml.map 供我们使用。就像List.mapMaybe.mapCmd.mapHtml.map 采用函数a -&gt; b 并使用它分别将Html as 或Cmd as 转换为Html bs 或Cmd bs。

所以,您在这里真正需要做的就是在NotFound.content 上使用Html.mapPageNotFound

content : Model -> Html Msg
content model =
    main_ []
        [ case parse Route.parser model.url of
            Just path ->
                matchedRoute path

            Nothing ->
                NotFound.content |> Html.map PageNotFound
        ]

两个分支现在都将返回 Main.Msg 并且编译器应该很高兴 :)

顺便说一句,在 elm-spa-example 中,这正在完成 here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    相关资源
    最近更新 更多