Linux中國

JavaScript 路由器

構建單頁面應用(SPA)有許多的框架/庫,但是我希望它們能少一些。我有一個解決方案,我想共享給大家。

class Router {
    constructor() {
        this.routes = []
    }

    handle(pattern, handler) {
        this.routes.push({ pattern, handler })
    }

    exec(pathname) {
        for (const route of this.routes) {
            if (typeof route.pattern === 'string') {
                if (route.pattern === pathname) {
                    return route.handler()
                }
            } else if (route.pattern instanceof RegExp) {
                const result = pathname.match(route.pattern)
                if (result !== null) {
                    const params = result.slice(1).map(decodeURIComponent)
                    return route.handler(...params)
                }
            }
        }
    }
}
const router = new Router()

router.handle('/', homePage)
router.handle(/^/users/([^/]+)$/, userPage)
router.handle(/^//, notFoundPage)

function homePage() {
    return 'home page'
}

function userPage(username) {
    return `${username}'s page`
}

function notFoundPage() {
    return 'not found page'
}

console.log(router.exec('/')) // home page
console.log(router.exec('/users/john')) // john's page
console.log(router.exec('/foo')) // not found page

使用它你可以為一個 URL 模式添加處理程序。這個模式可能是一個簡單的字元串或一個正則表達式。使用一個字元串將精確匹配它,但是如果使用一個正則表達式將允許你做一些更複雜的事情,比如,從用戶頁面上看到的 URL 中獲取其中的一部分,或者匹配任何沒有找到頁面的 URL。

我將詳細解釋這個 exec 方法 … 正如我前面說的,URL 模式既有可能是一個字元串,也有可能是一個正則表達式,因此,我首先來檢查它是否是一個字元串。如果模式與給定的路徑名相同,它返回運行處理程序。如果是一個正則表達式,我們與給定的路徑名進行匹配。如果匹配成功,它將獲取的參數傳遞給處理程序,並返回運行這個處理程序。

工作示例

那個例子正好記錄到了控制台。我們嘗試將它整合到一個頁面,看看它是什麼樣的。

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Router Demo</title>
 <link rel="shortcut icon" href="data:,">
 <script src="/main.js" type="module"></script>
</head>
<body>
 <header>
 <a href="/">Home</a>
 <a href="/users/john_doe">Profile</a>
 </header>
 <main></main>
</body>
</html>

這是 index.html。對於單頁面應用程序來說,你必須在伺服器側做一個特別的工作,因為所有未知的路徑都將返回這個 index.html。在開發時,我們使用了一個 npm 工具調用了 serve。這個工具去提供靜態內容。使用標誌 -s/--single,你可以提供單頁面應用程序。

使用 Node.js 和安裝的 npm(它與 Node 一起安裝),運行:

npm i -g serve
serve -s

那個 HTML 文件將腳本 main.js 載入為一個模塊。在我們渲染的相關頁面中,它有一個簡單的 <header> 和一個 <main> 元素。

main.js 文件中:

const main = document.querySelector(&apos;main&apos;)
const result = router.exec(location.pathname)
main.innerHTML = result

我們調用傳遞了當前路徑名為參數的 router.exec(),然後將 result 設置為 main 元素的 HTML。

如果你訪問 localhost 並運行它,你將看到它能夠正常工作,但不是預期中的來自一個單頁面應用程序。當你點擊鏈接時,單頁面應用程序將不會被刷新。

我們將在每個點擊的鏈接的錨點上附加事件監聽器,防止出現預設行為,並做出正確的渲染。因為一個單頁面應用程序是一個動態的東西,你預期要創建的錨點鏈接是動態的,因此要添加事件監聽器,我使用的是一個叫 事件委託 的方法。

我給整個文檔附加一個點擊事件監聽器,然後去檢查在錨點上(或內部)是否有點擊事件。

Router 類中,我有一個註冊回調的方法,在我們每次點擊一個鏈接或者一個 popstate 事件發生時,這個方法將被運行。每次你使用瀏覽器的返回或者前進按鈕時,popstate 事件將被發送。

為了方便其見,我們給回調傳遞與 router.exec(location.pathname) 相同的參數。

class Router {
    // ...
    install(callback) {
        const execCallback = () => {
            callback(this.exec(location.pathname))
        }

        document.addEventListener(&apos;click&apos;, ev => {
            if (ev.defaultPrevented
                || ev.button !== 0
                || ev.ctrlKey
                || ev.shiftKey
                || ev.altKey
                || ev.metaKey) {
                return
            }

            const a = ev.target.closest(&apos;a&apos;)

            if (a === null
                || (a.target !== &apos;&apos; && a.target !== &apos;_self&apos;)
                || a.hostname !== location.hostname) {
                return
            }

            ev.preventDefault()

            if (a.href !== location.href) {
                history.pushState(history.state, document.title, a.href)
                execCallback()
            }
        })

        addEventListener(&apos;popstate&apos;, execCallback)
        execCallback()
    }
}

對於鏈接的點擊事件,除調用了回調之外,我們還使用 history.pushState() 去更新 URL。

我們將前面的 main 元素中的渲染移動到 install 回調中。

router.install(result => {
 main.innerHTML = result
})

DOM

你傳遞給路由器的這些處理程序並不需要返回一個字元串。如果你需要更多的東西,你可以返回實際的 DOM。如:

const homeTmpl = document.createElement(&apos;template&apos;)
homeTmpl.innerHTML = `
 <div class="container">
 <h1>Home Page</h1>
 </div>
`

function homePage() {
 const page = homeTmpl.content.cloneNode(true)
 // You can do `page.querySelector()` here...
 return page
}

現在,在 install 回調中,你可以去檢查 result 是一個 string 還是一個 Node

router.install(result => {
    if (typeof result === &apos;string&apos;) {
        main.innerHTML = result
    } else if (result instanceof Node) {
        main.innerHTML = &apos;&apos;
        main.appendChild(result)
    }
})

這些就是基本的功能。我希望將它共享出來,因為我將在下篇文章中使用到這個路由器。

我已經以一個 npm 包 的形式將它發布了。

via: https://nicolasparada.netlify.com/posts/js-router/

作者:Nicolás Parada 選題:lujun9972 譯者:qhwdw 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國