对不起,但是我是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’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
答案 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