mcpField
S
solid-router

solid-router

github.com/tanstack开发15.3k58 次安装4 天前更新
代码仓库

关于 solid-router

用于开发 `@tanstack/solid-router` 应用的技能,介绍文件路由、Solid 专用钩子与组件,以及常见的类型和响应式陷阱。

主要功能

  1. 01使用 Vite 配置文件路由
  2. 02使用路由钩子和组件
  3. 03处理导航与路由数据
  4. 04遵循 Solid 响应式访问器模式
  5. 05避免常见配置和类型错误

适用场景

  1. 01为 Solid 应用添加类型安全路由
  2. 02构建导航和基于路由的数据加载
  3. 03排查访问器与插件配置问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tanstack/router --skill solid-router

安装后在 Claude Code 中输入/solid-router确认已加载。

详细介绍

Solid Router(@tanstack/solid-router)

此技能基于 router-core。请先阅读 @tanstack/router-core#router-core,了解基础概念。

CRITICAL:TanStack Router 的类型完全通过推断得出。绝不进行类型转换,也不要为推断出的值添加类型注解。 CRITICAL:TanStack Router 以客户端为先。Loader 默认在客户端运行,而不是服务器上。 CRITICAL:大多数 Hook 都会返回 Accessor<T>——必须调用 Accessor(value())才能读取响应式值。这是与 React 版本最大的区别。 CRITICAL:不要将 @tanstack/solid-router 与 @solidjs/router 混淆。它们是完全不同的库,API 也各不相同。

完整设置:使用 Vite 进行基于文件的路由

1. 安装依赖
npm install @tanstack/solid-router
npm install -D @tanstack/router-plugin @tanstack/solid-router-devtools
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',
      autoCodeSplitting: true,
    }),
    solidPlugin(),
  ],
})
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="/" activeClass="font-bold">
          Home
        </Link>
        <Link to="/about" activeClass="font-bold">
          About
        </Link>
      </nav>
      <hr />
      <Outlet />
    </>
  )
}
4. 创建路由文件
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/')({
  component: HomePage,
})

function HomePage() {
  return <h1>Welcome 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')!,
)

Hook 参考

所有 Hook 均从 @tanstack/solid-router 导入。大多数会返回 Accessor<T>——调用返回值即可读取其值。

useRouter() — 返回 TRouter(不是 Accessor)
import { useRouter } from '@tanstack/solid-router'

function InvalidateButton() {
  const router = useRouter()
  return <button onClick={() => router.invalidate()}>Refresh data</button>
}
useRouterState() — 返回 Accessor<T>

公开整个状态,因此会带来性能开销。需要获取匹配项或 location 时,优先使用 useMatches 和 useLocation。

import { useRouterState } from '@tanstack/solid-router'

function LoadingIndicator() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  return (
    <Show when={isLoading()}>
      <div>Loading...</div>
    </Show>
  )
}
useNavigate() — 返回一个函数(不是 Accessor)
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 }) — 返回 Accessor<T>
import { useSearch } from '@tanstack/solid-router'

function Pagination() {
  const search = useSearch({ from: '/products' })
  return <span>Page {search().page}</span>
}
useParams({ from }) — 返回 Accessor<T>
import { useParams } from '@tanstack/solid-router'

function PostHeader() {
  const params = useParams({ from: '/posts/$postId' })
  return <h2>Post {params().postId}</h2>
}
useLoaderData({ from }) — 返回 Accessor<T>
import { useLoaderData } from '@tanstack/solid-router'

function PostContent() {
  const data = useLoaderData({ from: '/posts/$postId' })
  return <article>{data().post.content}</article>
}
useMatch({ from }) — 返回 Accessor<T>
import { useMatch } from '@tanstack/solid-router'

function PostDetails() {
  const match = useMatch({ from: '/posts/$postId' })
  return <div>{match().loaderData.post.title}</div>
}
其他 Hook

均从 @tanstack/solid-router 导入:

  • useMatches() — Accessor<Array<Match>>,所有处于激活状态的路由匹配项
  • useParentMatches() — Accessor<Array<Match>>,父路由匹配项
  • useChildMatches() — Accessor<Array<Match>>,子路由匹配项
  • useRouteContext({ from }) — Accessor<T>,来自 beforeLoad 的上下文
  • useLoaderDeps({ from }) — Accessor<T>,Loader 依赖项的值
  • useBlocker({ shouldBlockFn }) — 当存在未保存的更改时阻止导航
  • useCanGoBack() — Accessor<boolean>
  • useLocation() — Accessor<ParsedLocation>
  • useLinkProps({ to, params?, search? }) — 返回 ComponentProps<'a'>(不是 Accessor)
  • useMatchRoute() — 返回一个函数;调用该函数会返回 Accessor<false | Params>
  • useHydrated() — Accessor<boolean>

组件参考

RouterProvider
<RouterProvider 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>}
</Link>
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 渲染延迟数据:

import { Await } from '@tanstack/solid-router'
import { Suspense } from 'solid-js'

function PostWithComments() {
  const data = Route.useLoaderData()
  return (
    <Suspense fallback={<div>Loading...</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 }) => <div>Error: {error.message}</div>}
>
  <RiskyWidget />
</CatchBoundary>
其他组件
  • CatchNotFound — 捕获子项中的 notFound() 错误;fallback 接收错误数据
  • Block — 声明式导航拦截器;使用 shouldBlockFn 和 withResolver 实现自定义 UI
  • ScrollRestoration — 已弃用;请改用 createRouter 的 scrollRestoration: true 选项
  • ClientOnly — 仅在 hydration 后渲染子项;接受 fallback 属性
Block

声明式导航拦截器组件:

import { Block } from '@tanstack/solid-router'
;<Block shouldBlockFn={() => formIsDirty()} withResolver>
  {({ status, proceed, reset }) => (
    <Show when={status === 'blocked'}>
      <div>
        <p>Are you sure?</p>
        <button onClick={proceed}>Yes</button>
        <button onClick={reset}>No</button>
      </div>
    </Show>
  )}
</Block>
ScrollRestoration

在导航时恢复滚动位置:

import { ScrollRestoration } from '@tanstack/solid-router'
// In root route component
;<ScrollRestoration />
ClientOnly

仅在 hydration 后渲染子项:

import { ClientOnly } from '@tanstack/solid-router'
;<ClientOnly fallback={<div>Loading...</div>}>
  <BrowserOnlyWidget />
</ClientOnly>
Head 管理

底层使用 @solidjs/meta:

import { HeadContent, Scripts } from '@tanstack/solid-router'

function RootDocument(props) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {props.children}
        <Scripts />
      </body>
    </html>
  )
}

Solid 专属模式

使用 createLink 创建自定义 Link 组件
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>
  )
}
在 Router 状态中使用 Solid Primitives
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 上下文实现身份验证
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' })
  }
}

常见错误

1. CRITICAL:忘记调用 Accessor

Hook 会返回 Accessor<T>——必须调用它才能读取值。这是从 React 迁移时最常见的问题。

// 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. 高优先级:解构响应式值

解构会破坏 Solid 的响应式跟踪。

// WRONG — loses reactivity
const { page } = useSearch({ from: '/products' })()

// CORRECT — access through accessor
const search = useSearch({ from: '/products' })
<span>Page {search().page}</span>
3. 高优先级:在 beforeLoad 或 loader 中使用 React 钩子

beforeLoad 和 loader 不是组件,而是普通的异步函数。不能在其中使用任何钩子(React 或 Solid)。请改用路由器上下文传递状态。

4. 中优先级:插件目标设置错误

必须在路由器插件配置中设置 target: 'solid'。默认值为 'react'。

交叉引用

  • @tanstack/router-core#router-core — 查看所有针对特定领域模式的子技能(搜索参数、数据加载、导航、身份验证、SSR 等)

评论

支持 Markdown