476 - 《笔记:Tanstack Router》

发布于 2024年10月23日

陆陆续续看了一天,以下是笔记。我的感受是,Tanstack Router 绝对是 React 路由方案的首选,同时他不仅是路由,还围绕路由优化了 DX、性能、Server 等场景。我大概率会在下个版本的框架里用他。

1、约定式路由基于 @tanstack/router-generator 生成,可以用插件,或者安装 @tanstack/cli。

pnpm i @tanstack/router-cli -D
tsr generate
tsr watch

文件会生成到 src/routeTree.gen.ts 中。

扩展名为 .lazy.tsx 的路由文件通过单独的捆绑包进行懒加载,以尽可能减小主捆绑包的大小。

2、安装。

pnpm add @tanstack/react-router
pnpm add react react-dom typescript -D

3、脚手架。

pnpm create @tanstack/router

4、devtool 。

pnpm i @tanstack/router-devtools -D
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
<TanStackRouterDevtools />
<TanStackRouterDevtools initialIsOpen={false} />

dev 阶段按需引入的方法。

const TanStackRouterDevtools =
  process.env.NODE_ENV === 'production'
    ? () => null // Render nothing in production
    : React.lazy(() =>
        // Lazy load in development
        import('@tanstack/router-devtools').then((res) => ({
          default: res.TanStackRouterDevtools,
          // For Embedded Mode
          // default: res.TanStackRouterDevtoolsPanel
        })),
      );

<Suspense>
  <TanStackRouterDevtools />
</Suspense>

5、基于代码的路由。

import ReactDOM from 'react-dom/client';
import {
  Outlet,
  RouterProvider,
  Link,
  createRouter,
  createRoute,
  createRootRoute,
} from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools';

const rootRoute = createRootRoute({
  component: () =>
  <>
    <div>
      <Link to="/">Index</Link>
      <Link to="/about">About</Link>
    </div>
    <Outlet />
    <TanStackRouterDevtools />
  </>,
});
const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/',
  component: () => <div>Index</div>,
});
const aboutRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/about',
  component: () => <div>About</div>,
});
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]);
const router = createRouter({ routeTree });

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <RouterProvider router={router} />
);

6、为啥官方推荐基于约定的路由方式,而不推荐基于代码的路由方式?1)脚手架代码多,2)要定义 getParentRoute() 函数,复杂,3)要处理 lazy 按需加载的路由,复杂。

7、一些概念。

  • Root Route,根路由,没有 path,始终被 match,组件始终渲染
  • Static Route,静态路由,没有动态路径
  • Index Route,索引路由,父路由完全匹配且没有子路由匹配时使用
  • Dynamic Route Segments,以 $ 开头的就是,比如 $postId
  • Splat / Catch-All Routes,捕捉到的路径名会出现在参数对象的 _splat 属性中。比如 files/$ 如果 url 是 /files/documents/hello-world,那 _splat 属性会是 documents/hello-world
  • Pathless Routes,以 _ 开头的为无路径路由,用于增加一些额外的逻辑,不需要 match path,应用场景比如,1)添加布局组件,2)渲染子路由之前添加前置 loader,3)验证并向子路由提供 search params,4)为子路由提供 error component 或 pending component,5)为子路由提供 context。
  • Non-Nested Routes,比如 posts_.$postId.edit.tsx,因为加了 _ postfix,所以不共享相同的 posts 前缀,因此会被视为顶级路由
  • 404 / NotFound Routes,没有 path 和 id 。
  • Pathless Route Group Directories,无意义,纯粹组织性的,不会以任何方式影响路由树。
  • Outlet,出口组件,用于渲染下一个可能匹配的子路由。注:如果路由的组件未定义,则会自动渲染一个 <Outlet />

8、Code Splitting。

TanStack Router 将路由代码分成两部分,1)关键路由配置,2)非关键和 lazy 路由配置。注意:1)所有的 Component 都是 2,2)loader 是 1 不是 2,因为 loader 如果是 2,那加载数据的时机会很晚,同时 loader 通常(!)对尺寸影响不大。

在 tsr.config.json 里配置可开启自动 code splitting。TanStack Router 会根据关键和非关键路由配置自动对所有路由文件进行代码拆分。

{
  // ...
  "autoCodeSplitting": true,
}

9、导航。

1)Everything is relative 。
2)如果没有提供 from route id,则假设是 from 自 /
3)ToOptions

type ToOptions = {
  from: string
  to: string
  params: Record<string, unknown> | (prevParams) => {}
  search: Record<string, unknown> | (prevSearch) => {}
  hash?: string | (prevHash) => string
  state?: Record<string, unknown> | (prevState) => {}
};

params 和 search 的区别:params 是路径中的动态部分,而 search 是附加在URL路径后的查询参数。

每个 route 都有 to 属性,可以拿来导航用,以获得类型安全。

4)NavigateOptions 在 ToOptions 的基础上多了个 { replace?: boolean },任何实际执行导航的 API 都将使用此接口。

5)LinkOptions 在 NavigateOptions 的基础上扩展了其他的,适用于所有的 a 标签。

type LinkOptions = NavigateOptions & {
  target?: HTMLAnchorElement['target']
  activeOptions?: { exact?, includeHash?, includeSearch?, explicitUndefined? }
  preload?: false | 'intent'
  // default: 50
  preloadDelay?: number
  disabled?: boolean
};

6)导航 API 有 <Link>(最常用)、useNavigate()<Navigate>Router.navigate()(可以在任意地方调用)。

7)<Link> 组件用 LinkProps,他在 LinkOptions 的基础上多了一些属性。

type LinkProps = LinkOptions & {
  activeProps?: 
    | React.AnchorHTMLAttributes<HTMLAnchorElement>
    | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
  inactiveProps?:
    | React.AnchorHTMLAttributes<HTMLAnchorElement>
    | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
}

8)TODO:search params 的类型安全。

9)active 状态。首先,有 activeProps 和 inactiveProps,然后有 data-status 属性在 active 时其值为 active,可基于此来设计样式。比如。

div.foo[data-status="active"] {
  color: red;
}

10)ActiveOptions,决定当前链接是否处于 active 状态。比如首页链接通常要 exact: true 严格匹配,<Link activeOptions={{exact: true}} to="/" />

type ActiveOptions = {
  // default: false
  exact?: boolean
  // default: false
  includeHash?: boolean
  // default: true
  includeSearch?: boolean
  // default: false
  explicitUndefined?: boolean
};

11)把 isActive 传给后代可以这样。

const link = (
  <Link to="/blog/post">
    {({ isActive }) => {
      return (
        <>
          <span

内容预览已结束

此内容需要会员权限。请先登录以查看完整内容。