1 回答

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