发出 AJAX HTTP GET 请求后,如何处理浏览器刷新按钮的这种破坏行为?

我依靠“AJAX 调用”编写我的第一个单页网站大约一个星期,认为这些会给我一个“SPA”,然而,在某些时候,当我点击顶部的刷新按钮时 [纯粹是运气],我看到的是最新的AJAX调用的字符串结果,在浏览器的白页上,而不是我网站的HTML页面!

实际上,原因是我将浏览器的 URL 更改为window.history.pushState,然后请求直接发送到后端,而不是通过我的 JavaScript 代码。

事情(盲目尝试)到目前为止我已经尝试过(TL; DR-Caution:他们都没有工作):

  • 我以为我能做的第一件事就是删除历史记录!但是,这似乎不可能(单个重定向除外)。否则,如果一个网站可以欺负用户其他网站的历史记录,则可能会导致明显的隐私问题!

  • 下一个想法是改变浏览器的历史。我认为也许只将主页(包装所有为 AJAX 响应创建的 DOM 元素的页面)注入到 将使window.history浏览器忽略接下来发生的任何其他事情。我错了。

  • 我还想到了将整个 DOM 元素存储在一个变量中并将其推送到 的丑陋解决方案window.history,但随后检测刷新事件并将其与直接访问区分开来将是下一个挑战。

  • 我还尝试监听 beforeunload 事件,试图阻止刷新,然后自己更改位置(这样听起来像是实际刷新),但是在单击刷新按钮后更改浏览器的 url,因此新URL获胜当点击刷新按钮时,'影响浏览器已经向其发送请求的路由。

那么有没有一种方法可以用 vanilla JavaScript 来处理这个问题?或者也许我误会了一些简单的事情?或者更好的解决方案是使用 React、VueJS 等工具来“更轻松”地完成我的 SPA?这些工具都没有问题,我只是更喜欢更简单的方法。我想要的 SPA 类似于MongoDB 网站的文档页面

编辑:代码:

xhr.onreadystatechange = function () {

        if (xhr.readyState === 4) {

          if (xhr.status === 200) {

            data = JSON.parse(xhr.responseText);

            content_box.innerHTML = data.content;

            window.history.pushState({"content":data.content, "title": data.title + " | App Manual"}, data.title + " | App Manual", href); 


郎朗坤
浏览 102回答 1
1回答

莫回无

代码非常简单(尽管可以使用 fetch 或更简洁的 XHR 实现进一步简化)。HTML这里只有两件事与我们有关。一个是#content-box,我们将从我们的 API 加载的任何内容放置在这里。在我的版本中,它看起来像这样:<section id="content_box"></section>另一个是a用于内部路由的元素,它们必须具有link与之关联的类,如下所示:<ul>  <li><a class="link" href="/section-1">Link #1</a></li>  <li><a class="link" href="/section-2">Link #2</a></li></ul>JS我们从一般变量的一些基本初始化开始:const useHash = true;const apiUrl = 'https://lucasreta.com/stack-overflow/spa-vanilla-js/api';const routes = ['section-1', 'section-2'];const content_box = document.getElementById("content_box");useHash将决定我们是否应该使用 URL 的锚点(哈希),或者我们内部路由的最后一个参数。apiUrl设置我们简单 API 的基本 URL。routes定义我们应用程序的有效路径。content_box是我们将在没有数据的情况下更新的 DOM 元素。然后我们定义我们的异步信息获取器,它仍然是一个非常标准的 XHR 调用,类似于您已经拥有的(缺少错误处理):function get(page) {  const xhr = new XMLHttpRequest();  xhr.onreadystatechange = function() {    if (this.readyState == 4 && this.status == 200) {      data = JSON.parse(xhr.responseText);      content_box.innerHTML = data.content;      const title = `${data.title} | App Manual`;      window.history.pushState(        { 'content': data.content, 'title': title},        title,        useHash ?          `#${page}` :          page      );    }  };  xhr.open('GET', `${apiUrl}/${page}`, true);  xhr.send();}在这里,我们向我们的get函数发送一个名为 的参数page,该参数与我们将使用的 API 的端点以及我们将在状态和 URL 中使用的名称相匹配,以确定我们必须显示的内容。鉴于您在代码中显示的响应对象的简单性,我认为将整个内容和标题对象推入我们的历史并稍后从那里使用它是合适的。在更复杂的场景中,我们可能只需要存储page参数并向 API 发出新请求。现在我们必须处理修改单页应用程序状态的三个事件:// add event listener to linksconst links = document.getElementsByClassName('link');for(let i = 0; i < links.length; i++) {  links[i].addEventListener('click', function(event) {    event.preventDefault();    get(links[i].href.split('/').pop());  }, false);}// add event listener to history changeswindow.addEventListener("popstate", function(e) {  const state = e.state;  content_box.innerHTML = state.content;});// add ready event for initial load of our site(function(fn = function() {  const page = useHash ?    window.location.hash.split('#').pop() :    window.location.href.split('/').pop();  get(routes.indexOf(page) >= 0 ? page : routes[0]);}) {  if (document.readyState != 'loading'){    fn();  } else {    document.addEventListener('DOMContentLoaded', fn);  }})();首先,我们获取所有具有类的元素.link并为它们附加一个事件侦听器,以便在单击它们时停止默认事件,而是get使用 href 的最后一个参数调用我们的函数。因此,当我们单击上面列出的第一个链接时,我们将执行 GET 请求并将api.com/section-1我们应用程序的 URL 更新为app.com/section-1或app.com/#section-1。我的实施有两个局限性:API 和应用路由必须匹配。路由不能有多个参数。两者都是可修复的,我不会详细介绍,因为它脱离了简单 POC 的要点,但我必须指出这一点。第一个可以通过使用某种字典来修复,该字典将我们的路由匹配到它们应该获取的端点。第二个问题可以通过在我们的链接事件侦听器中使逻辑更复杂一些来解决,扩展简单links[i].href.split('/').pop()以包括所有预期的参数。接下来我们有历史变化的事件监听器。由于我们将 API 返回的内容存储在历史状态本身中,所以当历史发生变化时,我们所要做的就是content_box用我们的state.content.最后,我们有 ready 函数,在 DOM 最初结束加载时调用:我们检查我们的 URL 以获取最后一个参数或散列/锚点的值。然后我们验证我们从 URL 获得的内容是否存在于我们定义的内部路由数组中。如果是这样,我们将get其作为参数调用我们的函数。如果没有,我们就从数组中获取第一条路线并用它来调用它。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript