导航

我们刚刚了解了如何加载一个页面,但是如果我们正在制作如 package.elm-lang.org 这样的网站,该怎么办?它拥有众多(如:搜索自述文件文档)页面的工作原理都不一样。它如何做到的?

多个页面

最简单的方法是加载一组不同的 HTML 文件。进入主页?加载新的 HTML。进入 elm/core 文档?加载新的 HTML。进入 elm/json 文档?加载新的 HTML。

在 Elm 0.19 之前,这就是软件包网站所做的!它的工作原理简单。不过它也有一些缺点

  1. 空白屏幕。每次加载新 HTML 时,屏幕都会变白。我们可以进行良好的过渡吗?
  2. 冗余请求。每个软件包都有一个 docs.json 文件,但是它会在您每次访问 StringMaybe 等模块时加载该文件。我们能否在不同页面之间共享数据?
  3. 冗余代码。主页和文档共享许多函数,如 Html.textHtml.div。我们能否在不同页面之间共享此代码?

我们可以改善所有这三个问题!基本思想是只加载一次 HTML,然后巧妙地处理 URL 更改。

单个页面

我们可以使用 Browser.application 来创建我们的程序,而不是使用 Browser.elementBrowser.document 来避免在 URL 更改时加载新的 HTML

application :
  { init : flags -> Url -> Key -> ( model, Cmd msg )
  , view : model -> Document msg
  , update : msg -> model -> ( model, Cmd msg )
  , subscriptions : model -> Sub msg
  , onUrlRequest : UrlRequest -> msg
  , onUrlChange : Url -> msg
  }
  -> Program flags model msg

它扩展了 Browser.document 在三种重要情况下的功能。

当应用程序启动时init 从浏览器的导航栏获取当前 Url。这允许您根据 Url 显示不同的内容。

当有人单击某个链接,例如 <a href="/home">首页</a>,它会被截获为 UrlRequest。因此,为了不加载带有各种缺点的新 HTML,onUrlRequest 将为 update 创建一条消息,在其中你可以决定下一步要做什么。你可以保存滚动位置、保存数据、自己更改 URL 等等。

当 URL 更改时,新的 Url 会发送到 onUrlChange。生成的邮件会传送到 update,在其中你可以决定如何显示新页面。

因此,这些三个附加的内容让你完全控制 URL 更改,而不用加载新 HTML。让我们看看它的实际应用!

示例

我们将从基础 Browser.application 程序开始。它只会跟踪当前的 URL。现在浏览一下代码!几乎所有新的有趣内容都发生在 update 函数中,我们将在代码之后了解这些细节

import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Url



-- MAIN


main : Program () Model Msg
main =
  Browser.application
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    , onUrlChange = UrlChanged
    , onUrlRequest = LinkClicked
    }



-- MODEL


type alias Model =
  { key : Nav.Key
  , url : Url.Url
  }


init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
  ( Model key url, Cmd.none )



-- UPDATE


type 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


view : Model -> Browser.Document Msg
view model =
  { title = "URL Interceptor"
  , body =
      [ text "The current URL is: "
      , b [] [ text (Url.toString model.url) ]
      , ul []
          [ viewLink "/home"
          , viewLink "/profile"
          , viewLink "/reviews/the-century-of-the-self"
          , viewLink "/reviews/public-opinion"
          , viewLink "/reviews/shah-of-shahs"
          ]
      ]
  }


viewLink : String -> Html msg
viewLink path =
  li [] [ a [ href path ] [ text path ] ]

update 函数可以处理 LinkClickedUrlChanged 消息。LinkClicked 分支中有很多新内容,所以我们首先关注这一点!

UrlRequest

每当有人单击诸如 <a href="/home">/home</a> 的链接时,它会生成 UrlRequest

type UrlRequest
  = Internal Url.Url
  | External String

Internal 变量适用于停留在同一域中的任何链接。因此,如果你正在浏览 https://example.com,内部链接包括诸如 settings#privacy/homehttps://example.com/home//example.com/home 之类的内容。

External 变量适用于链接到不同域的任何链接。像 https://elm-lang.org/exampleshttps://static.example.comhttp://example.com/home 这样的链接都会转到不同的域。请注意,将协议从 https 更改为 http 会被视为不同域!

无论有人按了哪个链接,我们的示例程序都会创建一个 LinkClicked 消息,并将其发送到 update 函数。这就是我们看到大多数有趣的新代码的地方!

LinkClicked

我们的大部分 update 逻辑是决定如何使用这些 UrlRequest

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
      )

特别有趣的函数是 Nav.loadNav.pushUrl。这两个函数都来自 Browser.Navigation 模块,该模块完全是关于以不同方式更改 URL 的。我们正在使用该模块中最常用的两个函数

  • load 加载所有新 HTML。它等同于在 URL 栏中键入 URL 并按回车键。因此,无论在你的 Model 中发生什么都会被抛出,并且会加载一个全新的页面。
  • pushUrl 更改 URL,但不会加载新的 HTML。相反,它触发了我们自己处理的 UrlChanged 消息!它还会向“浏览器历史”添加一个条目,如此一来,人们单击 BACKFORWARD 按钮时,各项功能依旧正常运行。

那么回头看看 update 函数,我们现在可以更好地理解它是如何组合在一起的。当用户单击 https://elm-lang.org 链接时,我们收到一条 External 消息并使用 load 从这些服务器加载新的 HTML。但是,当用户单击 /home 链接时,我们收到一条 Internal 消息并使用 pushUrl 来更改 URL,但不会加载新的 HTML!

注意 1:在我们的示例中,InternalExternal 链接都立即产生命令,但这不是必需的!当有人单击 External 链接时,也许您希望在导航离开之前将文本框内容保存到您的数据库。或者,当有人单击 Internal 链接时,也许您希望使用 getViewport 保存滚动位置,以防他们稍后导航到 BACK。这一切都是可能的!这是一般的 update 函数,您可以延迟导航并执行任何您想做的事情。

注意 2:如果您想在他们返回 BACK 时还原“他们正在查看的内容”,则滚动位置并不完美。如果他们调整浏览器大小或重新调整设备方向,它可能会有很大偏差!因此,最好还是保存“他们正在查看的内容”。也许这意味着使用 getViewportOf 来准确了解当前屏幕上的内容。具体情况取决于应用程序的确切工作方式,所以我无法提供确切的建议!

UrlChanged

有几种方法可以获取 UrlChanged 消息。我们刚才看到 pushUrl 会产生这些消息,但是按下浏览器 BACKFORWARD 按钮也会产生这些消息。正如我刚才在说明中所说的,当您收到 LinkClicked 消息时,可能不会立即发出 pushUrl 命令。

因此,拥有一个单独的 UrlChanged 消息的好处是,无论 URL 如何或何时更改,都没有关系。您只需要知道它已经更改了!

在本示例中,我们只是存储了新的 URL,但在真正的 Web 应用程序中,您需要解析 URL 以确定要显示什么内容。这就是下一页的主要内容!

注意:我跳过了对 Nav.Key 的讨论,以尝试专注于更重要的概念。但我将在这里为感兴趣的人进行解释!

导航Key是创建能改变 URL 的导航命令(例如 pushUrl)的必需品。只有在用Browser.application创建程序时才能访问Key,以保证你的程序具备检测这些 URL 更改的能力。如果 Key 值在其他种类的程序中可用,那么毫无防备的程序员肯定会遇到一些恼人的错误,并艰辛地学到许多技巧!

因此,我们的Model为我们的Key加了一行。这是一个相当小的代价,却能帮助每个人避免极为隐晦的一类问题!

与 “” 匹配的 个结果

    没有与 “” 匹配的结果