@tanstack/solid-router 实战指南:Accessor 响应式路由与 Solid 绑定全解析

📅 发布时间:2026/9/16 22:17:09
@tanstack/solid-router 实战指南:Accessor 响应式路由与 Solid 绑定全解析
tanstack/solid-router 实战指南Accessor 响应式路由与 Solid 绑定全解析【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router导读本文围绕 tanstack/solid-router 的 Solid 绑定技能展开系统讲解如何将 TanStack Router 以AccessorT 响应式返回值、Solid 原生原语createSignal / createMemo / Show / For 等、createLink组件工厂与solidjs/meta头部管理的方式接入 Solid 应用。读者学完后将能完成从 Vite 文件路由脚手架搭建、全套 Hooks/Components 使用到避开「忘记调用 Accessor」「在 loader 中用 hooks」等高频误区的完整实战闭环。本仓库对应的技能元数据见 packages/solid-router/skills/_artifacts/skill_spec.md版本 1.166.2 已审查完整技能正文见 packages/solid-router/skills/solid-router/SKILL.md。三条必须牢记的 CRITICAL 原则阅读任何代码之前先建立三个心智模型类型完全推断FULLY INFERREDTanStack Router 的类型系统基于路由树自动推导禁止手写 cast 或给推断值加类型注解否则会破坏类型安全链路。客户端优先CLIENT-FIRSTloader 默认在客户端运行而非服务端。这是与 Next.js 等 SSR 框架的核心差异SSR 场景需显式使用 packages/solid-router/src/ssr/ 下的服务端渲染通道。绝大多数 Hooks 返回AccessorT必须先调用value()才能读到响应式值。这是与 React 版本最大的区别也是下文所有示例中反复出现的()调用的根源。另外务必区分两个同名库tanstack/solid-router与solidjs/router是完全不同的项目、完全不同的 API切勿混用。完整搭建基于 Vite 的文件路由1. 安装依赖npm install tanstack/solid-router npm install -D tanstack/router-plugin tanstack/solid-router-devtools从仓库 package.json 可以看到运行时依赖仅四个solidjs/meta头部管理、tanstack/router-core核心路由逻辑、tanstack/history历史栈、solid-primitives/refspeer 依赖为solid-js ^1.9.10Node 要求20.19。2. 配置 Vite 插件// vite.config.ts import { defineConfig } from vite import solidPlugin from vite-plugin-solid import { tanstackRouter } from tanstack/router-plugin/vite export default defineConfig({ plugins: [ // MUST come before solid plugin tanstackRouter({ target: solid, // 默认是 reactSolid 项目必须显式指定 autoCodeSplitting: true, }), solidPlugin(), ], })插件负责扫描src/routes生成routeTree.gen.ts。target: solid是高频失误点——默认值是react漏配会导致生成的文件路由类型与 Solid 绑定不匹配。3. 创建根路由// src/routes/__root.tsx import { createRootRoute, Link, Outlet } from tanstack/solid-router export const Route createRootRoute({ component: RootLayout, }) function RootLayout() { return ( nav Link to/ activeClassfont-boldHome/Link Link to/about activeClassfont-boldAbout/Link /nav hr / Outlet / / ) }4. 创建路由文件// src/routes/index.tsx import { createFileRoute } from tanstack/solid-router export const Route createFileRoute(/)({ component: HomePage, }) function HomePage() { return h1Welcome Home/h1 }5. 创建 Router 实例并注册类型// src/main.tsx import { render } from solid-js/web import { RouterProvider, createRouter } from tanstack/solid-router import { routeTree } from ./routeTree.gen const router createRouter({ routeTree }) // REQUIRED — without this, Link/useNavigate/useSearch have no type safety declare module tanstack/solid-router { interface Register { router: typeof router } } render( () RouterProvider router{router} /, document.getElementById(root)!, )Register接口声明是整个类型安全的开关没有它Link、useNavigate、useSearch等全部退化为string与unknown。createRouter在源码层面继承自RouterCore并将 Solid 的 store 工厂注入其中见 packages/solid-router/src/router.ts所有 Hooks 都通过该 store 读取路由状态。Hooks 参考分清「Accessor」与「函数」所有 Hooks 均从tanstack/solid-router导入。下表是返回类型的快速判定Hook返回类型读取方式useRouter()路由器实例非 Accessorrouter.invalidate()useNavigate()导航函数非 Accessornavigate({ to })useLinkProps()ComponentPropsa非 Accessor直接展开到auseMatchRoute()函数调用后返回Accessorfalse \| ParamsmatchRoute({ to })()其余大部分 HooksAccessorTvalue()useRouter()— 返回路由器实例import { useRouter } from tanstack/solid-router function InvalidateButton() { const router useRouter() return button onClick{() router.invalidate()}Refresh data/button }useRouterState()— 返回AccessorT它暴露整个路由状态因此有性能成本需要 matches 或 location 时优先用useMatches、useLocation。务必传select缩小订阅范围import { useRouterState } from tanstack/solid-router function LoadingIndicator() { const isLoading useRouterState({ select: (s) s.isLoading }) return ( Show when{isLoading()} divLoading.../div /Show ) }从源码看useRouterState在客户端通过createMemoreplaceEqualDeep实现细粒度更新——只有select结果真正变化时下游才会重渲染packages/solid-router/src/useRouterState.tsx同时 SSR 分支做了特殊处理只渲染一次不订阅 store。useNavigate()— 返回导航函数import { useNavigate } from tanstack/solid-router function AfterSubmit() { const navigate useNavigate() const handleSubmit async () { await saveData() navigate({ to: /posts/$postId, params: { postId: 123 } }) } return button onClick{handleSubmit}Save/button }useSearch({ from })— 返回AccessorTimport { useSearch } from tanstack/solid-router function Pagination() { const search useSearch({ from: /products }) return spanPage {search().page}/span }useParams({ from })— 返回AccessorTimport { useParams } from tanstack/solid-router function PostHeader() { const params useParams({ from: /posts/$postId }) return h2Post {params().postId}/h2 }useLoaderData({ from })— 返回AccessorTimport { useLoaderData } from tanstack/solid-router function PostContent() { const data useLoaderData({ from: /posts/$postId }) return article{data().post.content}/article }useMatch({ from })— 返回AccessorTimport { useMatch } from tanstack/solid-router function PostDetails() { const match useMatch({ from: /posts/$postId }) return div{match().loaderData.post.title}/div }源码验证useSearch、useParams、useLoaderData三个 Hook 在实现上都委托给useMatch分别见 useSearch.tsx、useParams.tsx、useLoaderData.tsx而useMatch最终用Solid.createMemo包装并对select结果做replaceEqualDeep去重useMatch.tsx。这意味着这些 Hook 天然具备 Solid 的细粒度响应式值变化时只触发真正依赖它的计算。其他 Hooks 一览useMatches()—AccessorArrayMatch全部活跃路由 matchuseParentMatches()—AccessorArrayMatch父级 matchuseChildMatches()—AccessorArrayMatch子级 matchuseRouteContext({ from })—AccessorT读取beforeLoad写入的上下文useLoaderDeps({ from })—AccessorTloader 依赖值用于 loader 缓存失效判定useBlocker({ shouldBlockFn })— 拦截导航保护未保存的修改useCanGoBack()—AccessorbooleanuseLocation()—AccessorParsedLocationuseMatchRoute()— 返回函数调用后返回Accessorfalse | Params用于「当前是否命中某路由」判断useHydrated()—Accessorboolean水合完成标记Components 参考RouterProviderRouterProvider router{router} /Link类型安全导航链接子节点可以是函数以响应激活态Link to/posts/$postId params{{ postId: 42 }} View Post /Link {/* Function children for active state */} Link to/about {(state) span classList{{ active: state.isActive }}About/span} /LinkLink底层由useLinkProps支撑内部用Solid.splitProps拆分发散 props如activeProps、preload、preloadDelay、resetScroll、viewTransition等并接入useHydrated与 IntersectionObserver 实现预加载packages/solid-router/src/link.tsx。Outlet渲染匹配到的子路由组件function Layout() { return ( div Sidebar / main Outlet / /main /div ) }Navigate声明式重定向在onMount中触发导航import { Navigate } from tanstack/solid-router function OldPage() { return Navigate to/new-page / }Await结合 Solid 的Suspense渲染 deferred 数据import { Await } from tanstack/solid-router import { Suspense } from solid-js function PostWithComments() { const data Route.useLoaderData() return ( Suspense fallback{divLoading.../div} Await promise{data().deferredComments} {(comments) For each{comments}{(c) li{c.text}/li}/For} /Await /Suspense ) }CatchBoundary包装Solid.ErrorBoundary的错误边界import { CatchBoundary } from tanstack/solid-router CatchBoundary getResetKey{() widget} errorComponent{({ error }) divError: {error.message}/div} RiskyWidget / /CatchBoundary其他组件CatchNotFound— 捕获子级notFound()错误fallback接收错误数据Block— 声明式导航拦截器配合shouldBlockFn与withResolver实现自定义确认 UIScrollRestoration—已废弃改用createRouter的scrollRestoration: true选项ClientOnly— 水合后才渲染子级接受fallbackpropBlock详解import { Block } from tanstack/solid-router Block shouldBlockFn{() formIsDirty()} withResolver {({ status, proceed, reset }) ( Show when{status blocked} div pAre you sure?/p button onClick{proceed}Yes/button button onClick{reset}No/button /div /Show )} /Block源码中Block的 resolver 状态是blocked | idle的判别联合blocked分支携带current、next两个完整位置对象含routeId、fullPath、params、search以及proceed/resetpackages/solid-router/src/useBlocker.tsx因此确认弹窗可以展示「从哪里跳到哪里」的完整上下文。ScrollRestoration旧方案import { ScrollRestoration } from tanstack/solid-router // In root route component ScrollRestoration /ClientOnlyimport { ClientOnly } from tanstack/solid-router ClientOnly fallback{divLoading.../div} BrowserOnlyWidget / /ClientOnly头部管理Head Management底层使用solidjs/meta见 package.json 的 dependenciesimport { HeadContent, Scripts } from tanstack/solid-router function RootDocument(props) { return ( html head HeadContent / /head body {props.children} Scripts / /body /html ) }Solid 专属模式Solid-Specific Patterns用createLink定制链接组件import { createLink } from tanstack/solid-router const StyledLinkComponent (props) ( a {...props} class{styled-link ${props.class ?? }} / ) const StyledLink createLink(StyledLinkComponent) function Nav() { return ( StyledLink to/posts/$postId params{{ postId: 42 }} Post /StyledLink ) }createLink与useLinkProps、linkOptions一同从 packages/solid-router/src/link.tsx 导出它把 TanStack Router 的类型安全to/params/search全部注入自定义组件同时保留原有激活态逻辑。用 Solid 原语消费路由状态import { createMemo, Show, For } from solid-js import { useRouterState } from tanstack/solid-router function Breadcrumbs() { const matches useRouterState({ select: (s) s.matches }) const crumbs createMemo(() matches().filter((m) m.context?.breadcrumb), ) return ( nav For each{crumbs()} {(match) span{match.context.breadcrumb}/span} /For /nav ) }基于 Router Context 的认证import { createRootRouteWithContext } from tanstack/solid-router const rootRoute createRootRouteWithContext{ auth: AuthState }()({ component: RootComponent, }) // In main.tsx — provide context at router creation const router createRouter({ routeTree, context: { auth: authState }, }) // In a route — access via beforeLoad (NOT hooks) beforeLoad: ({ context }) { if (!context.auth.isAuthenticated) { throw redirect({ to: /login }) } }注意认证状态通过路由创建时的 context注入而不是在组件里用 hooks 读取——因为beforeLoad/loader是普通异步函数运行在组件树之外。常见错误Failure Modes对照表技能规范 skill_spec.md 明确登记了本技能的 2 个失败模式扩展后共有 4 条高频陷阱#错误优先级修正1忘记调用 AccessorHIGH始终value()取值2解构响应式值HIGH通过 Accessor 读取3在 beforeLoad/loader 中使用 hooksHIGH改用 router context 传参4插件target配错MEDIUM显式设置target: solid1. 忘记调用 Accessor最高频Hooks 返回AccessorT必须调用才能读值——这是从 React 迁移到 Solid 的第一大坑// WRONG — comparing the accessor function, not its value const params useParams({ from: /posts/$postId }) if (params.postId 42) { ... } // params is a function! // CORRECT — call the accessor const params useParams({ from: /posts/$postId }) if (params().postId 42) { ... }2. 解构响应式值破坏响应性// WRONG — loses reactivity const { page } useSearch({ from: /products })() // CORRECT — access through accessor const search useSearch({ from: /products }) spanPage {search().page}/span解构会在读取瞬间把值「冻结」下来Solid 的依赖追踪随之失效页面将显示陈旧数据。这也是domain_map.yamlpackages/solid-router/skills/_artifacts/domain_map.yaml中「Unwrapping accessors incorrectly」失败模式的底层机制描述。3. 在 beforeLoad / loader 中使用 hooksbeforeLoad与loader不是组件而是普通异步函数React 或 Solid 的 hooks 都不能在其中使用。需要共享状态如用户信息时通过createRouter的context传入再在beforeLoad: ({ context })中读取。4. 插件 target 配置错误tanstackRouter()的target默认是reactSolid 项目必须显式写成solid否则生成的路由类型与组件绑定不匹配。与核心包的衔接tanstack/solid-router是薄绑定层所有路由核心逻辑匹配、导航、loader、类型推导都来自tanstack/router-coreRouterCore基类与routerStores注入见 packages/solid-router/src/router.ts历史管理来自tanstack/history导出清单可从 packages/solid-router/src/index.tsx 全量核对。更底层的路由模式search params 校验、数据加载、导航、认证、SSR 等在 router-core 技能 中展开——建议按 SKILL.md 的指引先读 router-core 再回来消化本文的 Solid 专属部分二者配合即可覆盖从类型系统到组件层的完整知识链。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考