Как создать SPA с Elm 0.19?
Я пытаюсь построить SPA с Elm и создать три страницы, которые должны показывать содержание, зависит от 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 но не смог понять, как он работает.
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 ->
ветка.
Проблема в том, что Msg
тип, на который ссылается NotFound.content
является NotFound.Msg
, Msg
тип, на который ссылается Main.matchedRoute
является Main.Msg
и они не объединяются автоматически. Поэтому, когда вы используете их в разных ветвях case
выражении, компилятор скажет вам, что они разные и не могут быть объединены в один тип для case
выражение, чтобы вернуться.
Таким образом, вы должны преобразовать одно в другое, и обычный способ сделать это - добавить вариант к "внешнему" типу msg (Main.Msg
), который оборачивает "внутренний" тип сообщения (NotFound.Msg
). К счастью, вы уже добавили этот вариант как PageNotFound NotFound.Msg
так что мы можем двигаться дальше.
Следующий шаг - сделать обертку NotFound.Msg
в PageNotFound
s. К сожалению, нам редко удается обработать значения NotFound.Msg
в одиночку, обычно в другом Html
или же Cmd
, что сложнее иметь дело с. К счастью, Эван знал достаточно заранее, чтобы предсказать этот сценарий, и добавил Cmd.map
а также Html.map
для нас, чтобы использовать. Как List.map
а также Maybe.map
, Cmd.map
а также Html.map
берет на себя функцию a -> b
и использует его для конвертации Html a
с или Cmd a
с Html b
с или 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
]
Теперь обе ветви вернутся Main.Msg
и компилятор должен быть счастлив:)
И, кстати, в elm-spa-example, это делается здесь