1 回答
TA贡献1865条经验 获得超7个赞
代码非常简单(尽管可以使用 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 links
const 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 changes
window.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
其作为参数调用我们的函数。如果没有,我们就从数组中获取第一条路线并用它来调用它。
添加回答
举报