Nano Stores Router
一个用于 Nano Stores 状态管理器的微型 URL 路由器。
- 小巧。 725 字节(压缩并经过 brotli 编码)。零依赖。
- 良好的 TypeScript 支持。
- 框架无关。可与 React、Preact、Vue、 Svelte、Angular、Solid.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,你可以使用 openPage
或 redirectPage:
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 可以在没有 window 和 location 的 Node 环境中使用。
在这种情况下,它始终返回 / 路径的路由。
您可以手动设置其他任何路由:
if (isServer) {
$router.open('/posts/demo/1')
}