我正在尝试使用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中的示例,但无法弄清楚它是如何工作的。
答案 0 :(得分:2)
您有多种Msg
类型,可以,但是会导致混乱。简而言之:Main.Msg
与NotFound.Msg
的类型不同。
函数matchedRoute
返回一个Html Main.Msg
,而函数NotFound.content
返回一个Html NotFound.Msg
;完全不同的类型。
您已经完成了99%的工作,因为您有一个PageNotFound NotFound.Msg
类型的构造函数,该构造函数会产生一个Main.Msg
。这使您可以将NotFound.Msg
包裹在Main.Msg
中。应该在您的PageNotFound NotFound.content
分支中做Nothing ->
。
答案 1 :(得分:1)
问题在于,Msg
引用的NotFound.content
类型是NotFound.Msg
,Msg
引用的Main.matchedRoute
类型是Main.Msg
,并且这些不会自动合并。因此,当您在case
表达式的不同分支中使用它们时,编译器会告诉您它们是不同的,并且无法统一为case
表达式返回的单一类型。
因此,您必须将一种转换为另一种,通常的方法是为“外部”味精类型(Main.Msg
)添加一个变体,以包装“内部”味精类型({{ 1}})。幸运的是,您已经将该变体添加为NotFound.Msg
,因此我们可以继续。
下一步是在PageNotFound NotFound.Msg
中包装NotFound.Msg
。不幸的是,我们很少单独处理PageNotFound
的值,它通常被包裹在NotFound.Msg
或Html
之类的其他类型中,这很难处理。幸运的是,埃文(Evan)有足够的预见力可以预测这种情况,并添加了Cmd
和Cmd.map
供我们使用。就像Html.map
和List.map
一样,Maybe.map
和Cmd.map
接受函数Html.map
并使用它来转换a -> b
s或Html a
分别到Cmd a
或Html b
s。
因此,您真正需要做的就是在Cmd b
上将Html.map
与PageNotFound
一起使用:
NotFound.content
两个分支现在都将返回content : Model -> Html Msg
content model =
main_ []
[ case parse Route.parser model.url of
Just path ->
matchedRoute path
Nothing ->
NotFound.content |> Html.map PageNotFound
]
,编译器应该很高兴:)
顺便说一句,在elm-spa-example中,这是here