476 - 《笔记:Tanstack Router》
陆陆续续看了一天,以下是笔记。我的感受是,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