ITADN
nanostores/router
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

Nano Stores Router

一个用于 Nano Stores 状态管理器的微型 URL 路由器。

  • 小巧。 725 字节(压缩并经过 brotli 编码)。零依赖。
  • 良好的 TypeScript 支持。
  • 框架无关。可与 ReactPreactVueSvelteAngularSolid.js 以及原生 JS 一起使用。

由于 Nano Stores 提倡将逻辑移至 store,因此路由器是一个 store, 而不是像 React 这样的 UI 框架中的组件。

// stores/router.ts
import { createRouter } from '@nanostores/router'

export const $router = createRouter({
  home: '/',
  list: '/posts/:category',
  post: '/posts/:category/:post'
})

在主动模式下,Store 监听 <a> 上的 document.body 和浏览器中的返回按钮点击。

// components/layout.tsx
import { useStore } from '@nanostores/react'

import { $router } from '../stores/router.js'

export const Layout = () => {
  const page = useStore($router)

  if (!page) {
    return <Error404 />
  } else if (page.route === 'home') {
    return <HomePage />
  } else if (page.route === 'list') {
    return <ListPage category={page.params.category} filters={page.search} />
  } else if (page.route === 'post') {
    return <PostPage post={page.params.post} />
  }
}

Nano Stores Router 由 Evil Martians 开发,这是一家专注于 开发者工具、AI 和网络安全初创企业 的美国设计与工程咨询公司。


安装

npm install nanostores @nanostores/router

用法

参见 Nano Stores 文档 关于在 UI 框架中使用 store 并订阅 store 变更的说明。

路由

Routes 是一个从路由名称到路由模式的对象:

createRouter({
  route1: '/',
  route2: '/path/:var1/and/:var2',
  route3: /\/posts\/(?<type>draft|new)\/(?<id>\d+)/
})

对于字符串模式,您可以使用 :name 表示变量部分。若要使参数可选,请使用 ? 修饰符:

createRouter({
  routeName: '/profile/:id?/:tab?'
})

路由可以具有 RegExp 模式。它们应该是一个数组,其中包含一个函数, 该函数将 () 组转换为键值对映射。

对于 TypeScript,路由参数将自动转换为类型。 您需要使用 TypeScript ≥5.x。

createRouter({
  routeName: '/path/:var1/and/:var2',
  routeName2: [/path2/, () => ({ num: 1, str: '' })]
})

/**
 * Params will be inferred as:
 * {
 *   routeName: { var1: string, var2: string },
 *   routeName2: { num: number, str: string }
 * }
 */

搜索查询路由

Router 值包含解析后的 URL 搜索参数(如 ?sort=name):

createRouter({ home: '/posts/:category' })

location.href = '/posts/general?sort=name'
router.get() //=> {
//                   path: '/posts/general',
//                   route: 'list',
//                   params: { category: 'general' },
//                   search: { sort: 'name' },
//                   hash: ''
//                 }

要禁用路由中搜索参数的自动解析,您需要 设置 search 选项。路由器现在会将搜索查询 ?a=1&b=2 视为字符串。参数的顺序将至关重要。

createRouter({ home: '/posts?page=general' }, { search: true })

location.href = '/posts/?page=general'
router.get() //=> {
//                   path: '/posts?page=general',
//                   route: 'list',
//                   params: { },
//                   search: { },
//                   hash: ''
//                 }

哈希路由

Router 的值当前为 location.hash,并且 router 会在哈希值变化时更新其值。

location.href = '/posts/general#dialog'
router.get() //=> {
//                   path: '/posts/general',
//                   route: 'list',
//                   params: { category: 'general' },
//                   search: {},
//                   hash: '#dialog'
//                 }

点击跟踪

默认情况下,路由器和 ?search 参数存储会在 window 上添加 click 事件监听器,以跟踪链接点击。

若要禁用特定链接的点击跟踪,请在链接标签中添加 target="_self"

<a href="/posts" target="_self">Posts</a>

您可以通过 links: false 选项禁用此行为,并创建自定义 <Link> 组件。

export const $router = createRouter({ … }, { links: false })

function onClick (e) {
  e.preventDefault()
  $router.open(new Url(e.target.href).pathname)
}

export const Link = (props) => {
  return <a onClick={onClick} {...props}></a>
}

URL 生成

使用 getPagePath() 可以避免在模板中硬编码 URL。最好将路由作为唯一的事实来源。

import { getPagePath } from '@nanostores/router'

…
  <a href={getPagePath($router, 'post', { category: 'guides', post: '10' })}>

如果需要以编程方式更改 URL,你可以使用 openPageredirectPage

import { openPage, redirectPage } from '@nanostores/router'

function requireLogin() {
  openPage($router, 'login')
}

function onLoginSuccess() {
  // Replace login route, so we don’t face it on back navigation
  redirectPage($router, 'home')
}

所有函数均接受搜索参数作为最后一个参数:

getPagePath($router, 'list', { category: 'guides' }, { sort: 'name' })
//=> '/posts/guides?sort=name'

防止导航

导航源自 store 值:URL 仅在 store 变更后才会改变。因此,你可以使用 Nano Stores onSet 事件来防止导航,例如,将用户保留在具有未保存更改的表单上。

import { onSet } from 'nanostores'

onSet($router, ({ newValue, abort }) => {
  if (hasUnsavedChanges && !confirm('Discard changes?')) {
    abort()
  }
})

abort() 停止 $router.open()openPage()redirectPage() 以及跟踪 <a> 点击:商店和 URL 都不会改变。

浏览器的前进/后退按钮会在 onSet 运行之前更改 URL, 因此 abort() 会将商店保持在旧值,但不会回滚 地址栏。如果需要,请在监听器中添加自己的 history.pushState

服务端渲染

Router 可以在没有 windowlocation 的 Node 环境中使用。 在这种情况下,它始终返回 / 路径的路由。

您可以手动设置其他任何路由:

if (isServer) {
  $router.open('/posts/demo/1')
}