- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
前端单页应用实现路由的策略有两种,分别是 基于hash 和 基于 History API 。
基于hash 。
通过将一个 URL path 用 # Hash 符号拆分。— 浏览器视作其为虚拟的片段.
最早前端路由的实现就是 基于 location.hash 来实现的, 有这样几个特性:
触发hash 变化的方式有两种:
基于 History API 。
普通的 URL path (无哈希的 URL path) — 服务器需要拦截路径请求返回 入口 index.html 文件 。
基于 hash 的实现,存在一些问题,例如 。
#
影响美观。 因此 H5 中,提供了 History API 来实现 URL 的变化。 采用这种策略实现的前端路由, 主要是利用了 popstate 事件来监听历史记录的变化.
补在文章后面:
除此之外, 基于 Hash 的路由不需要对服务器做改动,但是基于 History API 的路由则需要对服务器做一些 hanle 处理.
在开始手动实现之前,有必要先了解一下将会涉及的 API 与 方法.
返回 URL 中 # 部分的内容, 例如 http://www.example.com/index.html#hello 中的 #hello 部分.
window.hashchange
方法 该方法监听url 中, hash 部分改变时的回调.
注意⚠️: 用户点击 浏览器的前进后退 按钮,如果url 的hash 发生改变,同样也会触发该方法.
History.length
[只读属性] 返回 session 历史记录的长度,新打开的tab 页面该属性值为 1 .
History.scrollRestoration
控制页面刷新时是否记住用户页面的滚动位置。 该值是一个可设定值,有 auto 和 manual 两种:
auto
: 保存页面滚动位置(默认) manual
: 不保存页面滚动位置 新打开tab 页面时,滚动位置始终回到页面顶部。 这两个属性仅对 页面刷新有效.
History.state
[只读属性] 浏览器页面历史记录是一个栈结构,该属性将返回栈顶页面的 状态, 在使用 pushState() 或者 replaceState() 方法之前, 这个值为 null . state 是一个 js 对象,该对象将会被存储到用户的本地磁盘, 浏览器关闭重启之后,还能够访问历史值。 但是该对象有大小限制(具体大小根据浏览器的实现而定),一旦所设定内容超出该限制,浏览器将会抛出错误.
History.go()
该方法控制页面回到历史中的某个相对位置,仅一个可选参数表示前进的步进值,它可以为正值,也可以为负值.
0
, 则等同于页面刷新。 History.back()
该方法,不接受任何参数。 它等同于 history.go(-1) , 也就是页面的 回退 按钮.
History.forward()
该方法同样不接受参数,等同于 history.go(1) , 也就是页面的 前进 按钮 。
History.pushState()
和 History.replaceState()
语法:
pushState(state, unused)
pushState(state, unused, url)
state 是一个 js 对象,可以是任意被序列化的值。 第二个参数被废弃了,但是由于历史缘故,依然保留,在使用时,应该传入一个空字符串.
这两个方法用于手动操作History对象.
.
├── foo.html
└── index.html
<head>
<title>History demo</title>
</head>
<body>
<button onclick="handleClick()">test</button>
<p>
some text here...
</p>
</body>
function handleClick() {
window.history.pushState({ hello: 'world' }, '', 'foo.html');
}
chrome 浏览器中,可以将鼠标按住后退按钮,查看到 History 数组:
此时点击 test 按钮后
浏览器新打开tab 就会入栈一个 “New Tab”, 点击test 触发 pushState() 之后, 会将当且页面“History demo”入栈, 然后当前页面变为 foo.html , 页面的状态变作: {hello: 'world'} , 可以通过 history.state 访问到.
值得注意的是, 页面虽然改变了,但是还没有更新渲染。 此时如果刷新页面,就会更新渲染了.
但是注意 pushState 的目标url,必须是一个子域地址,且如果 pushState 的目标页面不存在,页面刷新之后会报404错误.
在 foo.html 中新增一个按钮,测试 replaceState 方法:
<!--foo.html-->
<body>
<button onclick="handleReplace()">replace</button>
foo foo foo foo foo foo foo foo foo foo
<script src="script.js"></script>
</body>
// script.js
function handleReplace() {
window.history.replaceState({ foo: 'bar' }, '', 'bar.html');
}
当前页面 foo.html 将会被替换为 bar.html , 状态改变为 {foo: 'bar'} , 页面同样在刷新后会更新.
注意, 该方法并不会新增一个记录到历史记录.
关于 History.pushState() 和 History.replaceState() 这两个API 的补充:
""
一个空串。 window.popstate
方法 该方法当用户通过点击 前进后退 按钮,或者通过js, 调用 history.go() , history.back() , history.forward() 时,将会被触发 。
了解了所必须的 API,下面详细的试试如何手动实现路由.
因为接下来的两种实现,我们都将用到同样的文件目录结构,所以在这里我们先创建好他们.
.
├── index.html
├── js
│ └── router.js
└── templates
├── 404.html
├── about.html
├── contact.html
└── index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" />
<title></title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>
<div id="content"></div>
<script src="js/router.js"></script>
</body>
</html>
注意一个步骤中的代码,在body闭合标签的上方,我们引入了js脚本.
将以下代码添加到 <nav></nav> 标签内,作为我们的路由导航.
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
// route.js
const routes = {
404: {
template: "/templates/404.html",
title: "404",
description: "Page not found",
},
"/": {
template: "/templates/index.html",
title: "Home",
description: "This is the home page",
},
"/about": {
template: "/templates/about.html",
title: "About Us",
description: "This is the about page",
},
"/contact": {
template: "/templates/contact.html",
title: "Contact Us",
description: "This is the contact page",
},
};
//script.js
const route = (event) => {
event = event || window.event; // get window.event if event argument not provided
event.preventDefault();
// window.history.pushState(state, unused, target link);
window.history.pushState({}, "", event.target.href);
locationHandler();
};
// create document click that watches the nav links only
document.addEventListener("click", (e) => {
const { target } = e;
if (!target.matches("nav a")) {
return;
}
e.preventDefault();
route();
});
//script.js
const locationHandler = async () => {
const location = window.location.pathname; // get the url path
// if the path length is 0, set it to primary page route
if (location.length == 0) {
location = "/";
}
// get the route object from the urlRoutes object
const route = routes[location] || routes["404"];
// get the html from the template
// 注意这里是怎么获取到html模板的,很具有技巧性
const html = await fetch(route.template).then((response) => response.text());
// set the content of the content div to the html
document.getElementById("content").innerHTML = html;
// set the title of the document to the title of the route
document.title = route.title;
// 如何选中 meta 标签
// set the description of the document to the description of the route
document
.querySelector('meta[name="description"]')
.setAttribute("content", route.description);
};
最后,我们需要在页面首次加载的时候调用一下 locationHandler 方法,否则,首页无法呈现.
此外,我们还需要添加 URL 变化的 watcher 。
//script.js
// add an event listener to the window that watches for url changes
window.onpopstate = locationHandler;
// call the urlLocationHandler function to handle the initial url
window.route = route;
// call the urlLocationHandler function to handle the initial url
locationHandler();
注意,点击导航,以及用户控制页面前进后退,都会触发页面的渲染。 所以需要调用 locationHandler 方法.
将以下内容添加在 <nav></nav> 标签内:
<a href="/">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
//script.js
const routes = {
404: {
template: "/templates/404.html",
title: "404",
description: "Page not found",
},
"/": {
template: "/templates/index.html",
title: "Home",
description: "This is the home page",
},
about: {
template: "/templates/about.html",
title: "About Us",
description: "This is the about page",
},
contact: {
template: "/templates/contact.html",
title: "Contact Us",
description: "This is the contact page",
},
};
//script.js
const locationHandler = async () => {
// get the url path, replace hash with empty string
var location = window.location.hash.replace("#", "");
// if the path length is 0, set it to primary page route
if (location.length == 0) {
location = "/";
}
// get the route object from the routes object
const route = routes[location] || routes["404"];
// get the html from the template
const html = await fetch(route.template).then((response) => response.text());
// set the content of the content div to the html
document.getElementById("content").innerHTML = html;
// set the title of the document to the title of the route
document.title = route.title;
// set the description of the document to the description of the route
document
.querySelector('meta[name="description"]')
.setAttribute("content", route.description);
};
同样的,页面首次加载,以及 hashchange 的时候都需要调用 locationHandler 函数 。
//script.js
// create a function that watches the hash and calls the urlLocationHandler
window.addEventListener("hashchange", locationHandler);
// call the urlLocationHandler to load the page
locationHandler();
总结的来说, 基于 History API 的实现,主要是利用了 h5 提供的 pushState , replaceState 方法。去改变当前页面的 URL, 同时,利用点击事件 结合 window.popState 监听事件触发页面的更新渲染逻辑.
而 基于 location.hash 的实现,则更为简单,直接 利用 a 便签的 href 属性,触发 hashchange 事件,进而触发页面的更新逻辑.
对比起来, 基于 location.hash 的实现要更为简单。 但是基于 History API 实现的路由,URL 中不含有 # 而显得用户体验更加良好.
此外值得注意的一点是, 现在的框架中,大都提供了这两中实现方案。 在实际应用中。 如果应用了 基于 History API 的实现方式, 服务器通常需要做一些配置 .
因为由于单页应用路由的实现是前端实现的, 可以理解为是 “伪路由”, 路由的跳转逻辑都是前端代码完成的,这样就存在一个问题, 例如上面的实现中, http://127.0.0.1:5500/about 这个页面用户点击了页面刷新,就会找不到页面。 因为浏览器会向服务器 “ http://127.0.0.1:5500/about” 这个地址发送 GET 请求, 希望请求到一个单独的 index.html 文件, 而实际上这个文件我们服务器上是不存在的。 我们需要将其处理为:
http://127.0.0.1:5500/ server 返回首页 。
http://127.0.0.1:5500/about server 返回首页, 然后前端路由跳转到 about 页 。
http://127.0.0.1:5500/contact server 返回首页, 然后前端路由跳转到 contact 页 。
为了做到这点,所以我们需要对服务器做一些转发处理, 。
// root 是我本地的页面地址
// try_files 将匹配子级路由全部尝试返回 index.html 文件
server {
listen 7000;
location / {
root /home/jayce/Desktop/temp/demo/front-end-router-implement/HistoryApi;
index index.html;
try_files $uri $uri/ /index.html;
}
}
/api/*
#
hash 后面的部分视作一个分页,因此默认的就不会触发页面的重载 搞不懂路由跳转?带你了解 history.js 实现原理 。
最后此篇关于SPA路由实现的基本原理的文章就讲到这里了,如果你想了解更多关于SPA路由实现的基本原理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
如何将十进制数字转换为mixed radix表示法? 我猜想给定每个基数数组的输入和十进制数,它应该输出每列值的数组。 最佳答案 伪代码: bases = [24, 60, 60] input = 8
我有 Table-A,其中有“x”行。 (对于这个例子有 8 行) 我通过使用游标创建了列数为“x”的Table-C。 (使其动态化;如果将更多行添加到 Table-A,则会在 Table-C 中创建
我有一个关于对象的(很可能是简单而愚蠢的)问题。我创建了实例“Person”的对象“jon”。当我打电话时 console.log(jon.name) 控制台会给我输出“jon”。到目前为止,一切都很
这个问题已经有答案了: 已关闭11 年前。 Possible Duplicate: javascript function vs. ( function() { … } ()); 抱歉,如果这太基础了
我正在尝试用 Java 重新创建射弹轨迹,但是,我遇到了一些问题。我看过很多解释公式之类的视频,但他们的方程中有一个目标,而我没有。我的意思是,他们有一个范围来计算子弹的下落,但我试图弄清楚子弹最终会
(希望如此)来自一个完整的 Rust 初学者的一个简单问题。我的循环有什么问题? num 计算结果为“69”的速度相当快,但是一旦 num 设置为“69”,循环就永远不会退出。我肯定遗漏了一些明显的东
我在 id="name"的元素上应用“.length”,但它计数为 29 而不是 14。我想知道我的错误在哪里?如果有人可以让我知道,那就太好了。谢谢! var name=document.getEl
我知道这很简单,但由于某种原因我无法让它工作。我正在尝试在 Java 中创建自定义颜色,但它似乎不起作用。 import java.awt.Color; Color deepGreen = new C
我有一个大文件,其中每一行都包含一个子字符串,例如 ABC123。如果我执行 grep ABC file.txt 或 grep ABC1 file.txt 我按预期返回这些行,但如果我执行 grep
我想将以下实体映射转换为 Priority 对象。在 getter 上,当我将“Short”更改为“Priority”并遵循 this.priority 时,它会提示 'basic' 属性类型不应该是
我正在开发一个相当基本的函数,我发现很难弄清楚为什么我会得到我的输出。 def mystery(n): print(n) if n < 4: my
我正在尝试对 WordPress 安装的新闻部分实现同位素过滤。我是 JavaScript/jQuery 的新手,正在尝试随时随地学习。我首先使用 Filters section of the Iso
已关闭。这个问题是 not reproducible or was caused by typos 。目前不接受答案。 这个问题是由拼写错误或无法再重现的问题引起的。虽然类似的问题可能是 on-top
我在另一个实体类中引用一个实体并收到此错误。下面是示例代码。我在 persistence.xml 中也有这些类。 是什么导致了这个问题?我正在使用 Spring 数据 JPA 和 Hibernate。
我正在解析 HTML 并重新格式化图像以使其更好地适应。由于某种原因,当我有多个图像需要解析时,我会超出范围,而且我一生都无法弄清楚为什么。 当 imgArray.count >1 时,我将使用带有递
我是 SQL 新手,正在尝试创建一个基本的子查询。我需要找出经理的平均年龄和实习生的平均年龄之间的差异。 标题为一栏 - 经理或实习生年龄是一列,全部在同一个表中。 我会使用两个子查询来做类似的事情:
我习惯了 csh,所以不得不使用 bash 有点烦人。这段代码有什么问题? if[$time > 0300] && [$time 和 300 && time < 900 )) then mod
我建立了这个页面:http://excelwrestling.com/poola.php即将到来的双重锦标赛。我的大部分数据都是从我的 mySQL 数据库中提取的,现在只有一些示例数据。 我希望链接选
是否有任何原因导致以下内容不起作用: for (i=0;i < someArray.length;i++) { if (someArray[i].indexOf("something") !=
我现在正在学习 Javascript,有一个问题一直困扰着我! 因此,我在这里所需要做的就是在此输入框中键入颜色,单击按钮并将标题更改为键入的颜色(仅当键入的颜色位于变量中指定的数组中时)。 我的代码
我是一名优秀的程序员,十分优秀!