SolidJS Router 如何使用?有哪些高级特性?
SolidJS Router 是 SolidJS 官方的客户端路由库,基于细粒度响应式系统构建,支持嵌套路由、数据预加载、懒加载和 SSR。它与 React Router、Vue Router 有何不同?核心差异在于路由状态天然响应式,数据获取与路由切换真正并行。下面从基础用法到高级特性逐层展开。
基本使用
安装 @solidjs/router 后,在应用入口用 Router 包裹路由定义:
jsximport { render } from "solid-js/web"; import { Router, Route } from "@solidjs/router"; import Home from "./pages/Home"; import About from "./pages/About"; render( () => ( <Router> <Route path="/" component={Home} /> <Route path="/about" component={About} /> </Router> ), document.getElementById("app") );
需要注意,新版本中不再需要 Routes 组件,Route 直接作为 Router 的子组件使用。
根级布局
通过 Router 的 root 属性指定根布局组件,适合放置导航栏、侧边栏和全局 Context Provider。根布局在路由切换时不会重新渲染,这是性能优化的重要一环:
jsxfunction Layout(props) { return ( <div> <nav> <A href="/">首页</A> <A href="/about">关于</A> </nav> <main>{props.children}</main> </div> ); } render(() => <Router root={Layout}> <Route path="/" component={Home} /> <Route path="/about" component={About} /> </Router>, document.getElementById("app"));
root 布局不会随路由切换而重新挂载,这使得全局状态(如登录态、主题切换)可以稳定地维持在布局层。
动态路由参数
使用冒号语法定义动态参数,通过 useParams 钩子响应式地访问参数值:
jsximport { useParams } from "@solidjs/router"; function UserProfile() { const params = useParams(); return <div>用户 ID: {params.id}</div>; } // 路由定义 <Route path="/users/:id" component={UserProfile} />
useParams 返回的 params 是响应式对象,参数变化时组件自动更新。这一点与 React Router 的 useParams 不同——SolidJS 的版本天然具备细粒度响应性,不需要借助额外的状态管理。
可选参数与通配符
在参数名后加问号声明可选参数,匹配有无该参数两种情况:
jsx<Route path="/stories/:id?" component={Stories} />
通配符用 * 匹配任意后代路径,必须是路径的最后一段,可以命名:
jsx<Route path="/docs/*rest" component={Docs} />
在组件内通过 params.rest 获取匹配的剩余路径,适合文档站、知识库等深层嵌套场景。
路由导航
SolidJS Router 提供三种导航方式,覆盖声明式和编程式两大场景:
声明式导航:A 组件会自动处理点击事件,并根据当前路径添加 active 状态类名,这是与普通 a 标签的核心区别:
jsximport { A } from "@solidjs/router"; <A href="/about" activeClass="current">关于</A>
编程式导航:useNavigate 返回导航函数,适合在异步操作完成后跳转:
jsximport { useNavigate } from "@solidjs/router"; function LoginButton() { const navigate = useNavigate(); const handleLogin = async () => { await login(); navigate("/dashboard", { replace: true }); }; return <button onClick={handleLogin}>登录</button>; }
navigate 的第二个参数支持 replace(替换历史记录,阻止用户回退)和 state(传递路由状态,目标页面通过 useLocation 获取)。
查询参数管理
useSearchParams 提供对 URL 查询参数的读写能力,常用于分页、筛选等场景:
jsximport { useSearchParams } from "@solidjs/router"; function ProductList() { const [searchParams, setSearchParams] = useSearchParams(); const page = () => parseInt(searchParams.page || "1"); return ( <div> <span>当前页: {page()}</span> <button onClick={() => setSearchParams({ page: page() + 1 })}> 下一页 </button> </div> ); }
setSearchParams 会合并更新查询参数,不会覆盖其他已有的参数。查询参数的变化也会触发响应式更新。
嵌套路由
嵌套路由让父组件包裹子路由,通过 props.children 渲染子路由内容。这是构建复杂页面布局的关键模式:
jsx<Router> <Route path="/users" component={UserLayout}> <Route path="/" component={UserList} /> <Route path="/:id" component={UserDetail} /> <Route path="/:id/edit" component={UserEdit} /> </Route> </Router> function UserLayout(props) { return ( <div class="user-section"> <Sidebar /> {props.children} </div> ); }
只有叶节点(最内层的 Route)会被渲染为独立路由,父路由承担布局职责。嵌套层级没有上限,但建议控制在 3 层以内以保持可维护性。
MatchFilter 路径参数验证
MatchFilter 可以对动态路径参数施加验证规则,不满足条件的路径不会匹配该路由。支持三种验证方式:
jsximport type { MatchFilters } from "@solidjs/router"; const filters: MatchFilters = { parent: ["mom", "dad"], // 枚举值——只匹配指定选项 id: /^\d+$/, // 正则表达式——只允许数字 slug: (v) => v.length > 3 && v.endsWith(".html"), // 自定义验证函数 }; <Route path="/users/:parent/:id/:slug" component={UserPage} matchFilters={filters} />
MatchFilter 的执行顺序是枚举 → 正则 → 自定义函数。未通过验证的路径会跳过该路由,继续匹配后续定义,这种机制避免了在组件内部做参数校验的额外开销。
路由守卫与权限控制
SolidJS Router 没有内置守卫 API,但通过组件组合可以灵活实现。核心思路是在父路由组件中检查认证状态,未认证则重定向:
jsximport { Outlet, useNavigate } from "@solidjs/router"; import { createEffect } from "solid-js"; function AuthGuard() { const navigate = useNavigate(); createEffect(() => { const token = sessionStorage.getItem("token"); if (!token) { navigate("/signin", { replace: true }); } }); return <Outlet />; } // 路由配置 <Route path="/dashboard" component={AuthGuard}> <Route path="/" component={Dashboard} /> <Route path="/settings" component={Settings} /> </Route>
这里用 Outlet 代替 props.children 渲染子路由,效果相同但语义更明确。认证检查放在 createEffect 中,当 token 状态响应式变化时会自动重新判断。
这种模式的优势在于:守卫逻辑与路由配置解耦,可以针对不同的路由层级应用不同的守卫策略(如管理员路由、付费用户路由等)。
懒加载
使用 Solid 的 lazy 函数实现路由级代码分割,只在路由被访问时加载对应组件,显著减少首屏加载体积:
jsximport { lazy, Suspense } from "solid-js"; const Dashboard = lazy(() => import("./pages/Dashboard")); const Settings = lazy(() => import("./pages/Settings")); <Suspense fallback={<Loading />}> <Route path="/dashboard" component={Dashboard} /> <Route path="/settings" component={Settings} /> </Suspense>
Suspense 包裹路由区域,在组件加载期间展示 fallback UI。与 React 的 Suspense 不同,Solid 的版本不依赖 Concurrent Mode,实现更轻量。
数据预加载(Preload)
Preload 是 SolidJS Router 区别于其他路由库的核心特性。它在路由加载或链接被悬停时提前获取数据,与组件懒加载并行执行,从根本上避免了数据瀑布流问题:
jsximport { query } from "@solidjs/router"; // query 创建带缓存的数据获取函数 export const getUser = query(async (id) => { const res = await fetch(`/api/users/${id}`); return res.json(); }, "getUser"); // preload 函数在路由匹配时或链接悬停时被调用 export function preloadUser({ params, location, intent }) { return getUser(params.id); } <Route path="/users/:id" component={UserDetail} preload={preloadUser} />
preload 函数接收三个参数:
- params:当前路由参数,与 useParams 返回值一致
- location:位置对象,包含 pathname、search、hash、query、state、key
- intent:触发原因,有四个值——initial(首次加载)、native(浏览器前进后退)、navigate(编程式导航)、preload(链接悬停预加载)
可以根据 intent 区分处理策略,比如只在 initial 和 navigate 时执行重量级数据获取,在 preload(悬停)时仅预取轻量数据。
query API 提供四层缓存机制:
- 服务端请求去重(请求生命周期内)
- 浏览器预加载缓存(5 秒)
- 响应式重新获取(基于 key 变化自动触发)
- 浏览器前进后退缓存(5 分钟)
这种多层缓存设计意味着:用户悬停链接时数据已经开始加载,点击时数据已就绪,回退时直接命中缓存,整个体验接近瞬时切换。
配置对象方式
除了 JSX 声明式配置,还支持以对象数组定义路由,适合需要动态生成路由的大型项目:
jsxconst routes = [ { path: "/users", component: lazy(() => import("./pages/users")), children: [ { path: "/:id", component: lazy(() => import("./pages/users/[id]")), preload: preloadUser, }, ], }, ]; render(() => <Router>{routes}</Router>, document.getElementById("app"));
配置对象方式与 JSX 方式功能完全等价,选择哪种取决于项目的代码组织偏好。
HashRouter 与 MemoryRouter
SolidJS Router 提供两种替代路由模式,应对不同的部署和测试需求:
- HashRouter:使用 URL hash 部分(#后面的内容)进行路由,不需要服务端配置重写规则,适合 GitHub Pages 等静态托管环境
- MemoryRouter:将路由历史保存在内存中,不操作浏览器 URL,适合单元测试和 Storybook 集成
jsximport { HashRouter, MemoryRouter } from "@solidjs/router"; // 静态部署——无需服务端配置 <HashRouter> <Route path="/" component={Home} /> </HashRouter> // 单元测试——不依赖浏览器 URL <MemoryRouter> <Route path="/" component={Home} /> </MemoryRouter>
404 与错误处理
使用 * 通配符定义兜底路由,处理所有未匹配的路径:
jsx<Route path="*404" component={NotFound} />
通配符路由可以在任意嵌套层级使用,确保子路由未匹配时也能正确兜底。命名通配符(如 *404 中的 "404")可以在组件内通过 useParams 获取。
useIsRouting 与路由过渡
useIsRouting 钩子返回布尔信号,指示路由是否正在过渡中,常用于顶部加载条:
jsximport { useIsRouting } from "@solidjs/router"; function App() { const isRouting = useIsRouting(); return ( <div> {isRouting() && <LoadingBar />} {/* 路由内容 */} </div> ); }
结合 Solid 的 Transition API,可以实现路由切换时的平滑过渡效果,新内容在加载完成后一次性替换,避免中间状态的闪烁。
核心要点总结
| 特性 | 用途 | 核心 API |
|---|---|---|
| 基本路由 | 路径与组件映射 | Router, Route |
| 根级布局 | 全局布局不重渲染 | root 属性 |
| 动态参数 | 路径参数提取 | useParams |
| 可选参数与通配符 | 灵活路径匹配 | ? 和 * 语法 |
| 路由导航 | 声明式与编程式跳转 | A, useNavigate |
| 查询参数 | URL search 读写 | useSearchParams |
| 嵌套路由 | 布局与子路由组合 | props.children, Outlet |
| 参数验证 | 约束路径参数格式 | MatchFilter |
| 路由守卫 | 认证与权限控制 | useNavigate, Outlet |
| 数据预加载 | 并行获取避免瀑布流 | preload, query |
| 懒加载 | 代码分割按需加载 | lazy, Suspense |
| 路由过渡 | 加载状态反馈 | useIsRouting |
| 替代模式 | 静态部署与测试 | HashRouter, MemoryRouter |
SolidJS Router 的设计哲学是将路由与 Solid 的细粒度响应式系统深度融合。preload + query 的组合让数据获取与路由切换真正并行,四层缓存机制覆盖了从悬停预取到浏览器回退的全部场景——这是区别于 React Router 和 Vue Router 的核心优势。