1 回答
TA貢獻(xiàn)1865條經(jīng)驗(yàn) 獲得超7個(gè)贊
代碼非常簡(jiǎn)單(盡管可以使用 fetch 或更簡(jiǎn)潔的 XHR 實(shí)現(xiàn)進(jìn)一步簡(jiǎn)化)。
HTML
這里只有兩件事與我們有關(guān)。一個(gè)是#content-box,我們將從我們的 API 加載的任何內(nèi)容放置在這里。在我的版本中,它看起來像這樣:
<section?id="content_box"></section>
另一個(gè)是a用于內(nèi)部路由的元素,它們必須具有link與之關(guān)聯(lián)的類,如下所示:
<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將決定我們是否應(yīng)該使用 URL 的錨點(diǎn)(哈希),或者我們內(nèi)部路由的最后一個(gè)參數(shù)。
apiUrl設(shè)置我們簡(jiǎn)單 API 的基本 URL。
routes定義我們應(yīng)用程序的有效路徑。
content_box是我們將在沒有數(shù)據(jù)的情況下更新的 DOM 元素。
然后我們定義我們的異步信息獲取器,它仍然是一個(gè)非常標(biāo)準(zhǔn)的 XHR 調(diào)用,類似于您已經(jīng)擁有的(缺少錯(cuò)誤處理):
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函數(shù)發(fā)送一個(gè)名為 的參數(shù)page,該參數(shù)與我們將使用的 API 的端點(diǎn)以及我們將在狀態(tài)和 URL 中使用的名稱相匹配,以確定我們必須顯示的內(nèi)容。
鑒于您在代碼中顯示的響應(yīng)對(duì)象的簡(jiǎn)單性,我認(rèn)為將整個(gè)內(nèi)容和標(biāo)題對(duì)象推入我們的歷史并稍后從那里使用它是合適的。在更復(fù)雜的場(chǎng)景中,我們可能只需要存儲(chǔ)page參數(shù)并向 API 發(fā)出新請(qǐng)求。
現(xiàn)在我們必須處理修改單頁應(yīng)用程序狀態(tài)的三個(gè)事件:
// 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并為它們附加一個(gè)事件偵聽器,以便在單擊它們時(shí)停止默認(rèn)事件,而是get使用 href 的最后一個(gè)參數(shù)調(diào)用我們的函數(shù)。
因此,當(dāng)我們單擊上面列出的第一個(gè)鏈接時(shí),我們將執(zhí)行 GET 請(qǐng)求并將api.com/section-1我們應(yīng)用程序的 URL 更新為app.com/section-1或app.com/#section-1。
我的實(shí)施有兩個(gè)局限性:
API 和應(yīng)用路由必須匹配。
路由不能有多個(gè)參數(shù)。
兩者都是可修復(fù)的,我不會(huì)詳細(xì)介紹,因?yàn)樗撾x了簡(jiǎn)單 POC 的要點(diǎn),但我必須指出這一點(diǎn)。第一個(gè)可以通過使用某種字典來修復(fù),該字典將我們的路由匹配到它們應(yīng)該獲取的端點(diǎn)。第二個(gè)問題可以通過在我們的鏈接事件偵聽器中使邏輯更復(fù)雜一些來解決,擴(kuò)展簡(jiǎn)單links[i].href.split('/').pop()以包括所有預(yù)期的參數(shù)。
接下來我們有歷史變化的事件監(jiān)聽器。由于我們將 API 返回的內(nèi)容存儲(chǔ)在歷史狀態(tài)本身中,所以當(dāng)歷史發(fā)生變化時(shí),我們所要做的就是content_box用我們的state.content.
最后,我們有 ready 函數(shù),在 DOM 最初結(jié)束加載時(shí)調(diào)用:
我們檢查我們的 URL 以獲取最后一個(gè)參數(shù)或散列/錨點(diǎn)的值。然后我們驗(yàn)證我們從 URL 獲得的內(nèi)容是否存在于我們定義的內(nèi)部路由數(shù)組中。如果是這樣,我們將get其作為參數(shù)調(diào)用我們的函數(shù)。如果沒有,我們就從數(shù)組中獲取第一條路線并用它來調(diào)用它。
添加回答
舉報(bào)
