5月27日 16:05

SolidJS Router 如何使用?有哪些高级特性?

SolidJS Router 是 SolidJS 官方的客户端路由库,基于细粒度响应式系统构建,支持嵌套路由、数据预加载、懒加载和 SSR。它与 React Router、Vue Router 有何不同?核心差异在于路由状态天然响应式,数据获取与路由切换真正并行。下面从基础用法到高级特性逐层展开。

基本使用

安装 @solidjs/router 后,在应用入口用 Router 包裹路由定义:

jsx
import { 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。根布局在路由切换时不会重新渲染,这是性能优化的重要一环:

jsx
function 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 钩子响应式地访问参数值:

jsx
import { 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 标签的核心区别:

jsx
import { A } from "@solidjs/router"; <A href="/about" activeClass="current">关于</A>

编程式导航:useNavigate 返回导航函数,适合在异步操作完成后跳转:

jsx
import { 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 查询参数的读写能力,常用于分页、筛选等场景:

jsx
import { 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 可以对动态路径参数施加验证规则,不满足条件的路径不会匹配该路由。支持三种验证方式:

jsx
import 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,但通过组件组合可以灵活实现。核心思路是在父路由组件中检查认证状态,未认证则重定向:

jsx
import { 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 函数实现路由级代码分割,只在路由被访问时加载对应组件,显著减少首屏加载体积:

jsx
import { 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 区别于其他路由库的核心特性。它在路由加载或链接被悬停时提前获取数据,与组件懒加载并行执行,从根本上避免了数据瀑布流问题:

jsx
import { 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 提供四层缓存机制:

  1. 服务端请求去重(请求生命周期内)
  2. 浏览器预加载缓存(5 秒)
  3. 响应式重新获取(基于 key 变化自动触发)
  4. 浏览器前进后退缓存(5 分钟)

这种多层缓存设计意味着:用户悬停链接时数据已经开始加载,点击时数据已就绪,回退时直接命中缓存,整个体验接近瞬时切换。

配置对象方式

除了 JSX 声明式配置,还支持以对象数组定义路由,适合需要动态生成路由的大型项目:

jsx
const 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 集成
jsx
import { 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 钩子返回布尔信号,指示路由是否正在过渡中,常用于顶部加载条:

jsx
import { 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 的核心优势。

标签:SolidJS