如何制作简单的Elmish路由器?

时间:2019-03-03 15:02:11

标签: f# fable-f# elmish

对不起,但是我是Fable和F#的新手。我从SAFE project开始了一个样板,并创建了一个包含两页的SPA。但是,所有逻辑都在一个文件内。我的问题是。如何实现将每个视图放在一个文件中的路由器?

我会这样:

...
Client
  |_Client.fs
  |_Pages
      |_ Home.fs
      |_ About.fs
Server
  |_Server.fs
...

下面是我的Client.fs文件

src / Client / Client.fs

(**
 - title: Navigation demo
 - tagline: The router sample ported from Elm
*)
module App

open Fable.Core
open Fable.Import
open Elmish
open Fable.Import.Browser
open Fable.PowerPack
open Elmish.Browser.Navigation
open Elmish.Browser.UrlParser


JsInterop.importAll "whatwg-fetch"

// Types
type Page = Home | Blog of int | Search of string

type Model =
  { page : Page
    query : string
    cache : Map<string,string list> }

let toHash =
    function
    | Blog id -> "#blog/" + (string id)
    | _ -> "#home"

/// The URL is turned into a Page option.
let pageParser : Parser<Page->_,_> =
  oneOf
    [ map Home (s "home")
      map Blog (s "blog" </> i32) ]


type Msg =
  | Query of string
  | Enter
  | FetchFailure of string*exn
  | FetchSuccess of string*(string list)


type Place = { ``place name``: string; state: string; }

(* If the URL is valid, we just update our model or issue a command.
If it is not a valid URL, we modify the URL to whatever makes sense.
*)
let urlUpdate (result:Option<Page>) model =
  match result with
  | Some page ->
      { model with page = page; query = "" }, []

  | None ->
      Browser.console.error("Error parsing url")
      ( model, Navigation.modifyUrl (toHash model.page) )

let init result =
  urlUpdate result { page = Home; query = ""; cache = Map.empty }


(* A relatively normal update function. The only notable thing here is that we
are commanding a new URL to be added to the browser history. This changes the
address bar and lets us use the browser&rsquo;s back button to go back to
previous pages.
*)
let update msg model =
  match msg with
  | Query query ->
      { model with query = query }, []

  | FetchFailure (query,_) ->
      { model with cache = Map.add query [] model.cache }, []

  | FetchSuccess (query,locations) ->
      { model with cache = Map.add query locations model.cache }, []


// VIEW

open Fable.Helpers.React
open Fable.Helpers.React.Props


let viewLink page description =
  a [ Style [ Padding "0 20px" ]
      Href (toHash page) ]
    [ str description]

let internal centerStyle direction =
    Style [ Display "flex"
            FlexDirection direction
            AlignItems "center"
            unbox("justifyContent", "center")
            Padding "20px 0" ]

let words size message =
  span [ Style [ unbox("fontSize", size |> sprintf "%dpx") ] ] [ str message ]

let internal onEnter msg dispatch =
    function
    | (ev:React.KeyboardEvent) when ev.keyCode = 13. ->
        ev.preventDefault()
        dispatch msg
    | _ -> ()
    |> OnKeyDown

let viewPage model dispatch =
  match model.page with
  | Home ->
      [ words 60 "Welcome!"
        str "Play with the links and search bar above. (Press ENTER to trigger the zip code search.)" ]

  | Blog id ->
      [ words 20 "This is blog post number"
        words 100 (string id) ]

open Fable.Core.JsInterop

let view model dispatch =
  div []
    [ div [ centerStyle "row" ]
        [ viewLink Home "Home"
          viewLink (Blog 42) "Cat Facts"
          viewLink (Blog 13) "Alligator Jokes"
          viewLink (Blog 26) "Workout Plan" ]
      hr []
      div [ centerStyle "column" ] (viewPage model dispatch)
    ]

open Elmish.React
open Elmish.Debug

// App
Program.mkProgram init update view
|> Program.toNavigable (parseHash pageParser) urlUpdate
|> Program.withReact "elmish-app"
|> Program.withDebugger
|> Program.run

1 个答案:

答案 0 :(得分:0)

通常,所有Elmish“组件”(您都可以将其理解为“文件”)具有:

  • 一个Model代表他们的状态
  • Msg代表组件中支持的可能操作
  • 一个update函数对Msg做出反应,并根据先前的Model状态生成一个新的Model
  • 一个view函数,用于从当前Model状态生成视图

在我的应用程序中,我使用以下结构,该结构使我可以(无限期地)扩展应用程序。

一个Router.fs文件,负责处理以代表不同的路由和parsing函数。

let inline (</>) a b = a + "/" + string b

type Route =
    | Home
    | Blog of int

let toHash (route : Route) =
    match route with
    | Home -> "home"
    | Blog id -> "blog" </> id

open Elmish.Browser.Navigation
open Elmish.Browser.UrlParser

let routeParser : Parser<Route -> Route, Route> =
    oneOf [ // Auth Routes
            map (fun domainId -> Route.Blog domainId) (s "blog" </> i32)
            map Route.Home (s "home")
            // Default Route
            map Route.Home top ]

一个Main.fs文件,负责创建Elmish程序并处理如何对路线更改做出反应。

open Elmish
open Fable.Helpers.React
open Fable.Import

type Page =
    | Home of Home.Model
    | Blog of Blog.Model
    | NotFound

type Model =
    { ActivePage : Page
      CurrentRoute : Router.Route option }

type Msg =
    | HomeMsg of Home.Msg
    | BlogMsg of Blog.Msg

let private setRoute (optRoute: Router.Route option) model =
    let model = { model with CurrentRoute = optRoute }

    match optRoute with
    | None ->
        { model with ActivePage = Page.NotFound }, Cmd.none

    | Some Router.Route.Home ->
        let (homeModel, homeCmd) = Home.init ()
        { model with ActivePage = Page.Home homeModel }, Cmd.map HomeMsg homeCmd

    | Some (Router.Route.Blog blogId) ->
        let (blogModel, blogCmd) = Blog.init blogId
        { model with ActivePage = Page.Blog blogModel }, Cmd.map BlogMsg blogCmd

let init (location : Router.Route option) =
    setRoute location
        { ActivePage = Page.NotFound
          CurrentRoute = None }

let update (msg : Msg) (model : Model) =
    match model.ActivePage, msg with
    | Page.NotFound, _ ->
        // Nothing to do here
        model, Cmd.none

    | Page.Home homeModel, HomeMsg homeMsg ->
        let (homeModel, homeCmd) = Home.update homeMsg homeModel
        { model with ActivePage = Page.Home homeModel }, Cmd.map HomeMsg homeCmd

    | Page.Blog blogModel, BlogMsg blogMsg ->
        let (blogModel, blogCmd) = Blog.update blogMsg blogModel
        { model with ActivePage = Page.Blog blogModel }, Cmd.map BlogMsg blogCmd

    | _, msg ->
        Browser.console.warn("Message discarded:\n", string msg)
        model, Cmd.none


let view (model : Model) (dispatch : Dispatch<Msg>) =
    match model.ActivePage with
    | Page.NotFound ->
        str "404 Page not found"

    | Page.Home homeModel ->
        Home.view homeModel (HomeMsg >> dispatch)

    | Page.Blog blogModel ->
        Blog.view blogModel (BlogMsg >> dispatch)

open Elmish.Browser.UrlParser
open Elmish.Browser.Navigation
open Elmish.React

// App
Program.mkProgram init update view
|> Program.toNavigable (parseHash Router.routeParser) setRoute
|> Program.withReactUnoptimized "elmish-app"
|> Program.run

因此,在您的情况下,我将拥有以下文件:

  • Router.fs
  • Home.fs
  • Blog.fs
  • Main.fs