Как создать SPA с Elm 0.19? - PullRequest
       37

Как создать SPA с Elm 0.19?

0 голосов
/ 27 сентября 2018

Я пытаюсь создать СПА с Elm и создать три страницы, которые должны показывать содержание, зависит от URL.

enter image description here

Содержание этих трех страниц аналогично, например 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 ]

0 голосов
/ 13 октября 2018

Проблема в том, что тип Msg, на который ссылается NotFound.content, равен NotFound.Msg, тип Msg, на который указывает Main.matchedRoute, равен Main.Msg, и они не объединяются автоматически.Поэтому, когда вы используете их в разных ветвях выражения case, компилятор скажет вам, что они различны и не могут быть объединены в один тип для возврата выражения case.

Итак, вынеобходимо преобразовать одно в другое, и обычный способ сделать это - добавить вариант к «внешнему» типу сообщения (Main.Msg), который переносит «внутренний» тип сообщения (NotFound.Msg).К счастью, вы уже добавили этот вариант как PageNotFound NotFound.Msg, так что мы можем двигаться дальше.

Следующий шаг - сделать обтекание NotFound.Msg с в PageNotFound с.К сожалению, нам редко удается обрабатывать только значения 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, и компилятор должен бытьhappy:)

И, кстати, в elm-spa-примере это делается здесь

0 голосов
/ 28 сентября 2018

У вас есть несколько типов 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 ->.

...