JavaScript 路由器
创始人
2024-03-01 23:28:02
0

构建单页面应用(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 模式既有可能是一个字符串,也有可能是一个正则表达式,因此,我首先来检查它是否是一个字符串。如果模式与给定的路径名相同,它返回运行处理程序。如果是一个正则表达式,我们与给定的路径名进行匹配。如果匹配成功,它将获取的参数传递给处理程序,并返回运行这个处理程序。

工作示例

那个例子正好记录到了控制台。我们尝试将它整合到一个页面,看看它是什么样的。




 
 
 Router Demo
 
 


 
Home Profile

这是 index.html。对于单页面应用程序来说,你必须在服务器侧做一个特别的工作,因为所有未知的路径都将返回这个 index.html。在开发时,我们使用了一个 npm 工具调用了 serve。这个工具去提供静态内容。使用标志 -s/--single,你可以提供单页面应用程序。

使用 Node.js 和安装的 npm(它与 Node 一起安装),运行:

npm i -g serve
serve -s

那个 HTML 文件将脚本 main.js 加载为一个模块。在我们渲染的相关页面中,它有一个简单的

和一个
元素。

main.js 文件中:

const main = document.querySelector('main')
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('click', ev => {
            if (ev.defaultPrevented
                || ev.button !== 0
                || ev.ctrlKey
                || ev.shiftKey
                || ev.altKey
                || ev.metaKey) {
                return
            }

            const a = ev.target.closest('a')

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

            ev.preventDefault()

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

        addEventListener('popstate', execCallback)
        execCallback()
    }
}

对于链接的点击事件,除调用了回调之外,我们还使用 history.pushState() 去更新 URL。

我们将前面的 main 元素中的渲染移动到 install 回调中。

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

DOM

你传递给路由器的这些处理程序并不需要返回一个字符串。如果你需要更多的东西,你可以返回实际的 DOM。如:

const homeTmpl = document.createElement('template')
homeTmpl.innerHTML = `
 

Home Page

` 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 === 'string') {
        main.innerHTML = result
    } else if (result instanceof Node) {
        main.innerHTML = ''
        main.appendChild(result)
    }
})

这些就是基本的功能。我希望将它共享出来,因为我将在下篇文章中使用到这个路由器。

我已经以一个 npm 包 的形式将它发布了。


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

作者:Nicolás Parada 选题:lujun9972 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

SPA

相关内容

Spark IDEA开发R...
目录 一.软件环境 二.开发环境准备 1.scala编译环境搭建 ...
2025-06-01 17:02:37
大数据之Spark环境搭建
文章目录前言一、Spark软件安装(一)...
2025-05-31 00:28:09
Spark学习笔记【基础概...
文章目录前言Spark基础Spark是什么spark和hadoop...
2025-05-30 15:35:30
SparkSQL - Da...
        DataFrame 创建 DataFrame SQ...
2025-05-28 16:57:08
【ChatGPT 论文阅读...
【SciSpace】ChatGPT 论文阅读神器1. 【SciSp...
2025-05-28 06:49:04
石头科技上榜大摩“中国AI...
近期,摩根士丹利研究发布《中国-人工智能:沉睡的巨人觉醒》报告,首...
2025-05-22 15:19:02

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
2024 开年,LLUG 和你... Hi,Linuxer,2024 新年伊始,不知道你是否已经准备好迎接新的一年~ 2024 年,Lin...
什么是 KDE Connect... 什么是 KDE Connect?它的主要特性是什么?它应该如何安装?本文提供了基本的使用指南。科技日...
Opera 浏览器内置的 VP... 昨天我们报道过 Opera 浏览器内置了 VPN 服务,用户打开它可以防止他们的在线活动被窥视。不过...