皇上,还记得我吗?我就是1999年那个Linux伊甸园啊-----24小时滚动更新开源资讯,全年无休!

JavaScript 路由器

构建单页面应用(SPA)有许多的框架/库,但是我希望它们能少一些。我有一个解决方案,我想共享给大家。

  1. class Router {
  2. constructor() {
  3. this.routes = []
  4. }
  5. handle(pattern, handler) {
  6. this.routes.push({ pattern, handler })
  7. }
  8. exec(pathname) {
  9. for (const route of this.routes) {
  10. if (typeof route.pattern === 'string') {
  11. if (route.pattern === pathname) {
  12. return route.handler()
  13. }
  14. } else if (route.pattern instanceof RegExp) {
  15. const result = pathname.match(route.pattern)
  16. if (result !== null) {
  17. const params = result.slice(1).map(decodeURIComponent)
  18. return route.handler(...params)
  19. }
  20. }
  21. }
  22. }
  23. }
  24. const router = new Router()
  25. router.handle('/', homePage)
  26. router.handle(/^\/users\/([^\/]+)$/, userPage)
  27. router.handle(/^\//, notFoundPage)
  28. function homePage() {
  29. return 'home page'
  30. }
  31. function userPage(username) {
  32. return `${username}'s page`
  33. }
  34. function notFoundPage() {
  35. return 'not found page'
  36. }
  37. console.log(router.exec('/')) // home page
  38. console.log(router.exec('/users/john')) // john's page
  39. console.log(router.exec('/foo')) // not found page

使用它你可以为一个 URL 模式添加处理程序。这个模式可能是一个简单的字符串或一个正则表达式。使用一个字符串将精确匹配它,但是如果使用一个正则表达式将允许你做一些更复杂的事情,比如,从用户页面上看到的 URL 中获取其中的一部分,或者匹配任何没有找到页面的 URL。

我将详细解释这个 exec 方法 … 正如我前面说的,URL 模式既有可能是一个字符串,也有可能是一个正则表达式,因此,我首先来检查它是否是一个字符串。如果模式与给定的路径名相同,它返回运行处理程序。如果是一个正则表达式,我们与给定的路径名进行匹配。如果匹配成功,它将获取的参数传递给处理程序,并返回运行这个处理程序。

工作示例

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Router Demo</title>
  7. <link rel="shortcut icon" href="data:,">
  8. <script src="/main.js" type="module"></script>
  9. </head>
  10. <body>
  11. <header>
  12. <a href="/">Home</a>
  13. <a href="/users/john_doe">Profile</a>
  14. </header>
  15. <main></main>
  16. </body>
  17. </html>

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

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

  1. npm i -g serve
  2. serve -s

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

main.js 文件中:

  1. const main = document.querySelector('main')
  2. const result = router.exec(location.pathname)
  3. main.innerHTML = result

我们调用传递了当前路径名为参数的 router.exec(),然后将 result 设置为 main 元素的 HTML。

如果你访问 localhost 并运行它,你将看到它能够正常工作,但不是预期中的来自一个单页面应用程序。当你点击链接时,单页面应用程序将不会被刷新。

我们将在每个点击的链接的锚点上附加事件监听器,防止出现缺省行为,并做出正确的渲染。因为一个单页面应用程序是一个动态的东西,你预期要创建的锚点链接是动态的,因此要添加事件监听器,我使用的是一个叫 事件委托 的方法。

我给整个文档附加一个点击事件监听器,然后去检查在锚点上(或内部)是否有点击事件。

Router 类中,我有一个注册回调的方法,在我们每次点击一个链接或者一个 popstate 事件发生时,这个方法将被运行。每次你使用浏览器的返回或者前进按钮时,popstate 事件将被发送。

为了方便其见,我们给回调传递与 router.exec(location.pathname) 相同的参数。

  1. class Router {
  2. // ...
  3. install(callback) {
  4. const execCallback = () => {
  5. callback(this.exec(location.pathname))
  6. }
  7. document.addEventListener('click', ev => {
  8. if (ev.defaultPrevented
  9. || ev.button !== 0
  10. || ev.ctrlKey
  11. || ev.shiftKey
  12. || ev.altKey
  13. || ev.metaKey) {
  14. return
  15. }
  16. const a = ev.target.closest('a')
  17. if (a === null
  18. || (a.target !== '' && a.target !== '_self')
  19. || a.hostname !== location.hostname) {
  20. return
  21. }
  22. ev.preventDefault()
  23. if (a.href !== location.href) {
  24. history.pushState(history.state, document.title, a.href)
  25. execCallback()
  26. }
  27. })
  28. addEventListener('popstate', execCallback)
  29. execCallback()
  30. }
  31. }

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

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

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

DOM

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

  1. const homeTmpl = document.createElement('template')
  2. homeTmpl.innerHTML = `
  3. <div class="container">
  4. <h1>Home Page</h1>
  5. </div>
  6. `
  7. function homePage() {
  8. const page = homeTmpl.content.cloneNode(true)
  9. // You can do `page.querySelector()` here...
  10. return page
  11. }

现在,在 install 回调中,你可以去检查 result 是一个 string 还是一个 Node

  1. router.install(result => {
  2. if (typeof result === 'string') {
  3. main.innerHTML = result
  4. } else if (result instanceof Node) {
  5. main.innerHTML = ''
  6. main.appendChild(result)
  7. }
  8. })

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

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


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

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

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

编译自:https://nicolasparada.netlify.com/posts/js-router/ 作者: Nicolás Parada
原创:LCTT https://linux.cn/article-9815-1.html 译者: qhwdw